(function () { 'use strict'; const defaultModalPosition = 'follow'; const defaultBtnPosition = 'bottom_right'; const defaultBtnStyle = 'side_sticky'; // 获取当前脚本的域名 const currentScript = document.currentScript || document.querySelector('script[src*="widget-bot.js"]'); const ulrObj = new URL(currentScript.src) const widgetDomain = `${ulrObj.origin}${ulrObj.pathname}`.replace('/widget-bot.js', '') let widgetInfo = null; let widgetButton = null; let widgetModal = null; let isDragging = false; let dragOffset = { x: 0, y: 0 }; let currentTheme = 'light'; // 默认浅色主题 let customTriggerElement = null; // 自定义触发元素 let customTriggerHandler = null; // 自定义触发元素的事件处理函数 let dragAnimationFrame = null; // 拖拽动画帧ID let buttonSize = { width: 0, height: 0 }; // 缓存按钮尺寸 let initialPosition = { left: 0, top: 0 }; // 拖拽开始时的初始位置 let hasDragged = false; // 标记是否发生了拖拽 let dragStartPos = { x: 0, y: 0 }; // 拖拽开始时的鼠标位置 // 应用主题 function applyTheme(theme_mode) { currentTheme = theme_mode === 'dark' ? 'dark' : 'light'; updateThemeClasses(); } // 更新主题类名 function updateThemeClasses() { if (widgetButton) { widgetButton.setAttribute('data-theme', currentTheme); } if (widgetModal) { widgetModal.setAttribute('data-theme', currentTheme); } } // 获取挂件信息 async function fetchWidgetInfo() { if (widgetButton) { widgetButton.classList.add('loading'); } try { const response = await fetch(`${widgetDomain}/share/v1/app/widget/info`, { method: 'GET', headers: { 'Content-Type': 'application/json', }, credentials: 'same-origin' }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); widgetInfo = data.data.settings?.widget_bot_settings; // 验证返回的数据结构 if (!widgetInfo || typeof widgetInfo !== 'object') { throw new Error('Invalid widget info response'); } // 应用主题模式 if (widgetInfo.theme_mode) { applyTheme(widgetInfo.theme_mode); } // 根据 btn_style 创建不同的挂件 const btnStyle = widgetInfo.btn_style || defaultBtnStyle; if (btnStyle === 'btn_trigger') { createCustomTrigger(); } else { createWidget(); } } catch (error) { console.error('获取挂件信息失败:', error); // 使用默认值 widgetInfo = { btn_text: '在线客服', btn_logo: `''`, btn_style: defaultBtnStyle, btn_position: defaultBtnPosition, modal_position: defaultModalPosition, theme_mode: 'light' }; applyTheme(widgetInfo.theme_mode); createWidget(); } finally { if (widgetButton) { widgetButton.classList.remove('loading'); } } } // 应用按钮位置 function applyButtonPosition(button, position) { const pos = position || defaultBtnPosition; button.style.top = 'auto'; button.style.right = 'auto'; button.style.bottom = 'auto'; button.style.left = 'auto'; // 两种模式使用相同的默认位置:距离边缘16px,垂直方向190px switch (pos) { case 'top_left': button.style.top = '190px'; button.style.left = '16px'; break; case 'top_right': button.style.top = '190px'; button.style.right = '16px'; break; case 'bottom_left': button.style.bottom = '190px'; button.style.left = '16px'; break; case 'bottom_right': default: button.style.bottom = '190px'; button.style.right = '16px'; break; } } // 创建侧边吸附按钮 function createSideStickyButton() { widgetButton = document.createElement('div'); widgetButton.className = 'widget-bot-button widget-bot-side-sticky'; widgetButton.setAttribute('role', 'button'); widgetButton.setAttribute('tabindex', '0'); widgetButton.setAttribute('aria-label', `打开${widgetInfo.btn_text || '在线客服'}窗口`); widgetButton.setAttribute('data-theme', currentTheme); const buttonContent = document.createElement('div'); buttonContent.className = 'widget-bot-button-content'; // 侧边吸附显示图标和文字(btn_logo 以及 btn_text) const icon = document.createElement('img'); const defaultIconSrc = widgetDomain + '/favicon.png'; icon.src = widgetInfo.btn_logo ? (widgetDomain + widgetInfo.btn_logo) : defaultIconSrc; icon.alt = 'icon'; icon.className = 'widget-bot-icon'; icon.onerror = () => { // 如果当前不是 favicon.png,尝试使用 favicon.png 作为备用 if (icon.src !== defaultIconSrc) { icon.src = defaultIconSrc; } else { // 如果 favicon.png 也加载失败,隐藏图标 icon.style.display = 'none'; } }; buttonContent.appendChild(icon); // 添加文字 const textDiv = document.createElement('div'); textDiv.className = 'widget-bot-text'; textDiv.textContent = widgetInfo.btn_text || '在线客服'; // 设置固定宽度、自动换行和居中 textDiv.style.wordWrap = 'break-word'; textDiv.style.whiteSpace = 'normal'; textDiv.style.textAlign = 'center'; buttonContent.appendChild(textDiv); widgetButton.appendChild(buttonContent); // 应用位置 - 距离边缘16px,垂直方向190px const position = widgetInfo.btn_position || defaultBtnPosition; applyButtonPosition(widgetButton, position); // 设置 border-radius 为 24px(统一圆角) widgetButton.style.borderRadius = '24px'; // 添加事件监听器 widgetButton.addEventListener('click', handleButtonClick); widgetButton.addEventListener('mousedown', startDrag); widgetButton.addEventListener('keydown', handleKeyDown); // 添加触摸事件支持 widgetButton.addEventListener('touchstart', handleTouchStart, { passive: false }); widgetButton.addEventListener('touchmove', handleTouchMove, { passive: false }); widgetButton.addEventListener('touchend', handleTouchEnd); document.body.appendChild(widgetButton); } // 创建悬浮球按钮 function createHoverBallButton() { widgetButton = document.createElement('div'); widgetButton.className = 'widget-bot-button widget-bot-hover-ball'; widgetButton.setAttribute('role', 'button'); widgetButton.setAttribute('tabindex', '0'); widgetButton.setAttribute('aria-label', `打开${widgetInfo.btn_text || '在线客服'}窗口`); widgetButton.setAttribute('data-theme', currentTheme); const buttonContent = document.createElement('div'); buttonContent.className = 'widget-bot-button-content'; // 悬浮球只显示图标(btn_logo) const icon = document.createElement('img'); const defaultIconSrc = widgetDomain + '/favicon.png'; icon.src = widgetInfo.btn_logo ? (widgetDomain + widgetInfo.btn_logo) : defaultIconSrc; icon.alt = 'icon'; icon.className = 'widget-bot-icon widget-bot-hover-ball-icon'; icon.onerror = () => { // 如果当前不是 favicon.png,尝试使用 favicon.png 作为备用 if (icon.src !== defaultIconSrc) { icon.src = defaultIconSrc; } else { // 如果 favicon.png 也加载失败,隐藏图标 icon.style.display = 'none'; } }; buttonContent.appendChild(icon); widgetButton.appendChild(buttonContent); // 应用位置 - 距离边缘16px,垂直方向190px applyButtonPosition(widgetButton, widgetInfo.btn_position || defaultBtnPosition); // 添加事件监听器 widgetButton.addEventListener('click', handleButtonClick); widgetButton.addEventListener('mousedown', startDrag); widgetButton.addEventListener('keydown', handleKeyDown); // 添加触摸事件支持 widgetButton.addEventListener('touchstart', handleTouchStart, { passive: false }); widgetButton.addEventListener('touchmove', handleTouchMove, { passive: false }); widgetButton.addEventListener('touchend', handleTouchEnd); document.body.appendChild(widgetButton); } // 创建挂件按钮 function createWidget() { // 如果已存在,先删除 if (widgetButton) { widgetButton.remove(); } const btnStyle = widgetInfo.btn_style || defaultBtnStyle; if (btnStyle === 'hover_ball') { createHoverBallButton(); } else { createSideStickyButton(); } // 创建模态框 createModal(); // 触发显示动画 setTimeout(() => { widgetButton.style.opacity = '1'; }, 100); } // 创建自定义触发按钮 function createCustomTrigger() { const btnId = widgetInfo.btn_id; if (!btnId) { console.error('btn_trigger 模式需要提供 btn_id'); return; } let retryCount = 0; const maxRetries = 50; // 最多重试 50 次(5秒) // 绑定事件到元素 function attachTrigger(element) { if (!element) return; // 避免重复绑定 if (element.hasAttribute('data-widget-trigger-attached')) { return; } element.setAttribute('data-widget-trigger-attached', 'true'); customTriggerElement = element; // 创建事件处理函数并保存引用 customTriggerHandler = function (e) { e.preventDefault(); e.stopPropagation(); showModal(); }; // 绑定点击事件 element.addEventListener('click', customTriggerHandler); } // 尝试查找并绑定元素 function tryAttachTrigger() { const element = document.getElementById(btnId); if (element) { attachTrigger(element); createModal(); return true; } return false; } // 立即尝试一次 if (tryAttachTrigger()) { return; } // 如果元素还没加载,使用多种方式监听 function retryAttach() { if (tryAttachTrigger()) { return; } retryCount++; if (retryCount < maxRetries) { setTimeout(retryAttach, 100); } else { console.warn('自定义触发按钮未找到,已停止重试:', btnId); } } // 使用 MutationObserver 监听 DOM 变化 const observer = new MutationObserver(function (mutations) { if (tryAttachTrigger()) { observer.disconnect(); } }); // 开始观察 DOM 变化 observer.observe(document.body, { childList: true, subtree: true }); // 如果 DOM 已加载完成,立即开始重试 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', function () { setTimeout(retryAttach, 100); }); } else { setTimeout(retryAttach, 100); } // 延迟断开观察器(避免无限观察) setTimeout(function () { observer.disconnect(); }, 10000); // 10秒后断开 } // 处理按钮点击事件(区分点击和拖拽) function handleButtonClick(e) { // 如果发生了拖拽,不打开弹框 if (hasDragged) { e.preventDefault(); e.stopPropagation(); return; } showModal(); } // 键盘事件处理 function handleKeyDown(e) { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); showModal(); } } // 触摸事件处理 let touchStartPos = { x: 0, y: 0 }; function handleTouchStart(e) { const touch = e.touches[0]; touchStartPos = { x: touch.clientX, y: touch.clientY }; startDrag(e); } function handleTouchMove(e) { if (!isDragging) return; e.preventDefault() const touch = e.touches[0]; drag({ clientX: touch.clientX, clientY: touch.clientY }); } function handleTouchEnd(e) { const touch = e.changedTouches[0]; const distance = Math.sqrt( Math.pow(touch.clientX - touchStartPos.x, 2) + Math.pow(touch.clientY - touchStartPos.y, 2) ); // 只有在没有拖拽且移动距离很小的情况下才认为是点击 if (!hasDragged && distance < 10) { // 判断为点击事件 setTimeout(() => showModal(), 100); } stopDrag(); } // 创建模态框 function createModal() { // 如果已存在,先删除 if (widgetModal) { widgetModal.remove(); } widgetModal = document.createElement('div'); widgetModal.className = 'widget-bot-modal'; widgetModal.setAttribute('role', 'dialog'); widgetModal.setAttribute('aria-modal', 'true'); widgetModal.setAttribute('aria-labelledby', 'widget-modal-title'); widgetModal.setAttribute('data-theme', currentTheme); const modalPosition = widgetInfo.modal_position || defaultModalPosition; if (modalPosition === 'fixed') { widgetModal.classList.add('widget-bot-modal-fixed'); } const modalContent = document.createElement('div'); modalContent.className = 'widget-bot-modal-content'; if (modalPosition === 'fixed') { modalContent.classList.add('widget-bot-modal-content-fixed'); } // 创建关闭按钮(透明框) const closeBtn = document.createElement('button'); closeBtn.className = 'widget-bot-close-btn'; closeBtn.setAttribute('aria-label', '关闭窗口'); closeBtn.setAttribute('type', 'button'); // 创建一个内部元素来处理实际的点击事件(因为按钮设置了 pointer-events: none) const closeBtnArea = document.createElement('div'); closeBtnArea.style.width = '100%'; closeBtnArea.style.height = '100%'; closeBtnArea.style.pointerEvents = 'auto'; // 内部元素可以接收事件 closeBtnArea.style.cursor = 'pointer'; closeBtnArea.addEventListener('click', function (e) { e.preventDefault(); e.stopPropagation(); hideModal(); }); closeBtn.appendChild(closeBtnArea); // 创建iframe const iframe = document.createElement('iframe'); iframe.className = 'widget-bot-iframe'; iframe.src = `${widgetDomain}/widget`; iframe.setAttribute('title', `${widgetInfo.btn_text || '在线客服'}服务窗口`); iframe.setAttribute('allow', 'camera; microphone; geolocation'); iframe.setAttribute('sandbox', 'allow-same-origin allow-scripts allow-forms allow-popups allow-presentation'); modalContent.appendChild(closeBtn); modalContent.appendChild(iframe); widgetModal.appendChild(modalContent); document.body.appendChild(widgetModal); } // 检测是否为移动端 function isMobile() { return window.innerWidth <= 768 || /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); } // 智能定位弹框(follow模式) function positionModalFollow(modalContent) { if (!widgetButton || !modalContent) return; // 移动端强制居中显示 if (isMobile()) { modalContent.style.position = 'relative'; modalContent.style.top = 'auto'; modalContent.style.left = 'auto'; modalContent.style.right = 'auto'; modalContent.style.bottom = 'auto'; modalContent.style.margin = 'auto'; modalContent.style.width = 'calc(100% - 32px)'; modalContent.style.height = 'auto'; return; } requestAnimationFrame(() => { const buttonRect = widgetButton.getBoundingClientRect(); const windowWidth = window.innerWidth; const windowHeight = window.innerHeight; const margin = 16; // 距离屏幕边缘的最小距离 const buttonGap = 16; // 弹框和按钮之间的最小距离 // 先设置一个临时位置来获取弹框尺寸 const originalPosition = modalContent.style.position; const originalTop = modalContent.style.top; const originalLeft = modalContent.style.left; const originalVisibility = modalContent.style.visibility; const originalDisplay = modalContent.style.display; modalContent.style.position = 'absolute'; modalContent.style.top = '0'; modalContent.style.left = '0'; modalContent.style.visibility = 'hidden'; modalContent.style.display = 'block'; const modalRect = modalContent.getBoundingClientRect(); const modalWidth = modalRect.width; const modalHeight = modalRect.height; modalContent.style.visibility = originalVisibility || 'visible'; modalContent.style.display = originalDisplay || 'block'; // 计算按钮中心点 const buttonCenterX = buttonRect.left + buttonRect.width / 2; const buttonCenterY = buttonRect.top + buttonRect.height / 2; // 判断按钮在屏幕的哪一侧 const isLeftSide = buttonCenterX < windowWidth / 2; const isTopSide = buttonCenterY < windowHeight / 2; // 智能选择弹框位置,确保完整显示 let finalTop, finalBottom, finalLeft, finalRight; if (isLeftSide) { // 按钮在左侧,弹框优先显示在右侧(按钮右侧) finalLeft = buttonRect.right + buttonGap; finalRight = 'auto'; // 如果右侧空间不够,显示在左侧(按钮左侧) if (finalLeft + modalWidth > windowWidth - margin) { finalLeft = 'auto'; finalRight = windowWidth - buttonRect.left + buttonGap; // 如果左侧空间也不够,则贴左边(但保持与按钮的距离) if (buttonRect.left - buttonGap - modalWidth < margin) { finalLeft = margin; finalRight = 'auto'; } } } else { // 按钮在右侧,弹框优先显示在左侧(按钮左侧) finalLeft = 'auto'; finalRight = windowWidth - buttonRect.left + buttonGap; // 如果左侧空间不够,显示在右侧(按钮右侧) if (buttonRect.left - buttonGap - modalWidth < margin) { finalRight = 'auto'; finalLeft = buttonRect.right + buttonGap; // 如果右侧空间也不够,则贴右边(但保持与按钮的距离) if (finalLeft + modalWidth > windowWidth - margin) { finalLeft = 'auto'; finalRight = margin; } } } // 垂直方向:优先与按钮顶部对齐 // 弹框顶部与按钮顶部对齐 finalTop = buttonRect.top; finalBottom = 'auto'; // 如果弹框底部超出屏幕,则向上调整,确保弹框完整显示在屏幕内 if (finalTop + modalHeight > windowHeight - margin) { // 计算向上调整后的位置 const adjustedTop = windowHeight - margin - modalHeight; // 如果调整后的位置仍然在按钮上方,则使用调整后的位置 if (adjustedTop >= margin) { finalTop = adjustedTop; } else { // 如果调整后仍然超出,则贴顶部 finalTop = margin; } } else if (finalTop < margin) { // 如果弹框顶部超出屏幕,则贴顶部 finalTop = margin; } // 应用最终位置 modalContent.style.top = finalTop !== undefined ? (typeof finalTop === 'string' ? finalTop : finalTop + 'px') : 'auto'; modalContent.style.bottom = finalBottom !== undefined ? (typeof finalBottom === 'string' ? finalBottom : finalBottom + 'px') : 'auto'; modalContent.style.left = finalLeft !== undefined ? (typeof finalLeft === 'string' ? finalLeft : finalLeft + 'px') : 'auto'; modalContent.style.right = finalRight !== undefined ? (typeof finalRight === 'string' ? finalRight : finalRight + 'px') : 'auto'; // 最终检查并修正,确保弹框完全在屏幕内 requestAnimationFrame(() => { const finalModalRect = modalContent.getBoundingClientRect(); // 修正左边界 if (finalModalRect.left < margin) { modalContent.style.left = margin + 'px'; modalContent.style.right = 'auto'; } // 修正右边界 if (finalModalRect.right > windowWidth - margin) { modalContent.style.right = margin + 'px'; modalContent.style.left = 'auto'; } // 修正上边界 if (finalModalRect.top < margin) { modalContent.style.top = margin + 'px'; modalContent.style.bottom = 'auto'; } // 修正下边界 if (finalModalRect.bottom > windowHeight - margin) { modalContent.style.bottom = margin + 'px'; modalContent.style.top = 'auto'; } }); }); } // 显示模态框 function showModal() { if (!widgetModal) return; widgetModal.style.display = 'flex'; document.body.classList.add('widget-bot-modal-open'); const modalPosition = widgetInfo.modal_position || defaultModalPosition; const modalContent = widgetModal.querySelector('.widget-bot-modal-content'); // 移动端强制居中显示 if (isMobile()) { modalContent.style.position = 'relative'; modalContent.style.top = 'auto'; modalContent.style.left = 'auto'; modalContent.style.right = 'auto'; modalContent.style.bottom = 'auto'; modalContent.style.margin = 'auto'; modalContent.style.width = 'calc(100% - 32px)'; modalContent.style.height = 'auto'; } else if (modalPosition === 'fixed') { // 桌面端固定模式:居中展示 modalContent.style.position = 'relative'; modalContent.style.top = 'auto'; modalContent.style.left = 'auto'; modalContent.style.right = 'auto'; modalContent.style.bottom = 'auto'; modalContent.style.margin = 'auto'; } else { // 桌面端跟随模式:跟随按钮位置 - 智能定位,确保弹框完整显示在屏幕内 positionModalFollow(modalContent); } // 添加ESC键关闭功能(先移除避免重复绑定) document.removeEventListener('keydown', handleEscKey); document.addEventListener('keydown', handleEscKey); } // ESC键处理 function handleEscKey(e) { // 只在弹框显示时响应 ESC 键 if (e.key === 'Escape' && widgetModal && widgetModal.style.display === 'flex') { hideModal(); } } // 隐藏模态框 function hideModal() { if (!widgetModal) return; widgetModal.style.display = 'none'; document.body.classList.remove('widget-bot-modal-open'); // 恢复焦点到按钮 if (widgetButton) { widgetButton.focus(); } // 移除ESC键监听 document.removeEventListener('keydown', handleEscKey); } // 开始拖拽 function startDrag(e) { if (e.preventDefault) { e.preventDefault() }; isDragging = true; hasDragged = false; // 重置拖拽标记 const rect = widgetButton.getBoundingClientRect(); const clientX = e.clientX || (e.touches && e.touches[0].clientX); const clientY = e.clientY || (e.touches && e.touches[0].clientY); // 记录拖拽开始位置 dragStartPos.x = clientX; dragStartPos.y = clientY; // 由于 transform-origin 是 center,scale 不会改变元素中心位置 // 但 getBoundingClientRect() 返回的尺寸是放大后的,需要计算原始尺寸 // 假设当前可能有 scale(1.1),计算原始尺寸 const scale = 1.1; // hover 时的 scale 值 const originalWidth = rect.width / scale; const originalHeight = rect.height / scale; // 缓存按钮原始尺寸(未缩放) buttonSize.width = originalWidth; buttonSize.height = originalHeight; // 由于 transform-origin 是 center,元素的左上角位置需要考虑 scale 的影响 // 中心点位置不变,但左上角会向左上移动 const centerX = rect.left + rect.width / 2; const centerY = rect.top + rect.height / 2; const originalLeft = centerX - originalWidth / 2; const originalTop = centerY - originalHeight / 2; initialPosition.left = originalLeft; initialPosition.top = originalTop; // 计算鼠标相对于原始尺寸(未缩放)按钮左上角的偏移 dragOffset.x = clientX - originalLeft; dragOffset.y = clientY - originalTop; widgetButton.style.position = 'fixed'; widgetButton.style.top = originalTop + 'px'; widgetButton.style.left = originalLeft + 'px'; widgetButton.style.right = 'auto'; widgetButton.style.bottom = 'auto'; // 保持 scale 效果 widgetButton.style.transform = 'scale(1.1)'; widgetButton.style.transition = 'none'; widgetButton.style.willChange = 'left, top, transform'; document.addEventListener('mousemove', drag, { passive: false }); document.addEventListener('mouseup', stopDrag); widgetButton.classList.add('dragging'); widgetButton.style.zIndex = '10001'; } // 拖拽中 - 直接更新位置,实现丝滑跟随 function drag(e) { if (!isDragging) return; if (e.preventDefault) { e.preventDefault(); } const clientX = e.clientX || (e.touches && e.touches[0].clientX); const clientY = e.clientY || (e.touches && e.touches[0].clientY); // 检测是否发生了实际移动(超过5px才认为是拖拽) const moveDistance = Math.sqrt( Math.pow(clientX - dragStartPos.x, 2) + Math.pow(clientY - dragStartPos.y, 2) ); if (moveDistance > 5) { hasDragged = true; } const windowWidth = window.innerWidth; const windowHeight = window.innerHeight; const buttonWidth = buttonSize.width; const buttonHeight = buttonSize.height; // 直接基于鼠标位置计算新位置 // 鼠标位置减去拖拽偏移量,得到按钮左上角应该的位置 const newLeft = clientX - dragOffset.x; const newTop = clientY - dragOffset.y; // 垂直位置:限制在屏幕范围内,距离顶部和底部最小距离为 24px const minTop = 24; const maxTop = Math.max(minTop, windowHeight - buttonHeight - 24); const constrainedTop = Math.max(minTop, Math.min(newTop, maxTop)); // 水平位置:限制在屏幕范围内 const maxLeft = windowWidth - buttonWidth; const constrainedLeft = Math.max(0, Math.min(newLeft, maxLeft)); widgetButton.style.left = constrainedLeft + 'px'; widgetButton.style.top = constrainedTop + 'px'; widgetButton.style.right = 'auto'; widgetButton.style.bottom = 'auto'; // 保持 scale 效果 widgetButton.style.transform = 'scale(1.1)'; } // 停止拖拽 function stopDrag() { if (!isDragging) return; isDragging = false; // 取消待执行的动画帧 if (dragAnimationFrame) { cancelAnimationFrame(dragAnimationFrame); dragAnimationFrame = null; } document.removeEventListener('mousemove', drag); document.removeEventListener('mouseup', stopDrag); widgetButton.classList.remove('dragging'); widgetButton.style.zIndex = '9999'; // 恢复过渡效果 widgetButton.style.transition = ''; widgetButton.style.willChange = ''; // 移除 transform,让 CSS hover 效果可以正常工作 widgetButton.style.transform = ''; // 根据按钮类型和当前位置进行最终定位 requestAnimationFrame(() => { const buttonRect = widgetButton.getBoundingClientRect(); const currentLeft = buttonRect.left; const currentTop = buttonRect.top; const windowWidth = window.innerWidth; const windowHeight = window.innerHeight; const buttonWidth = buttonSize.width; const buttonHeight = buttonSize.height; // 两种模式使用相同的停止拖拽逻辑:只能左右侧边缘吸附 // 根据按钮实际位置判断左右,保持当前位置 const screenCenterX = windowWidth / 2; const buttonCenterX = currentLeft + buttonWidth / 2; const isLeftSide = buttonCenterX < screenCenterX; const sideDistance = 16; // 距离边缘的距离 // 垂直位置:保持在当前位置,限制在屏幕范围内,距离顶部和底部最小距离为 24px const minTop = 24; const maxTop = Math.max(minTop, windowHeight - buttonHeight - 24); const finalTop = Math.max(minTop, Math.min(currentTop, maxTop)); let finalLeft; // 水平位置:距离左右边16px if (isLeftSide) { finalLeft = sideDistance; widgetButton.style.left = sideDistance + 'px'; widgetButton.style.right = 'auto'; } else { finalLeft = windowWidth - sideDistance - buttonWidth; widgetButton.style.right = sideDistance + 'px'; widgetButton.style.left = 'auto'; } widgetButton.style.top = finalTop + 'px'; widgetButton.style.bottom = 'auto'; // 更新 border-radius(现在都是24px圆角) widgetButton.style.borderRadius = '24px'; // 更新初始位置,为下次拖拽做准备 if (finalLeft !== undefined && finalTop !== undefined) { initialPosition.left = finalLeft; initialPosition.top = finalTop; } else { // 如果未定义,使用当前实际位置 initialPosition.left = buttonRect.left; initialPosition.top = buttonRect.top; } }); } // 设置按钮状态 function setButtonState(state) { if (!widgetButton) return; widgetButton.classList.remove('success', 'error', 'loading'); if (state === 'success') { widgetButton.classList.add('success'); } else if (state === 'error') { widgetButton.classList.add('error'); } else if (state === 'loading') { widgetButton.classList.add('loading'); } } // 更新主题模式 function updateThemeMode(theme_mode) { if (theme_mode === 'light' || theme_mode === 'dark') { applyTheme(theme_mode); } } // 全局函数 window.hideWidgetModal = hideModal; window.setWidgetButtonState = setButtonState; window.updateWidgetTheme = updateThemeMode; // 点击模态框背景关闭 document.addEventListener('click', function (e) { if (e.target === widgetModal) { hideModal(); } }); // 窗口大小改变时重新定位 window.addEventListener('resize', function () { if (widgetModal && widgetModal.style.display === 'flex') { const modalContent = widgetModal.querySelector('.widget-bot-modal-content'); if (!modalContent) return; // 移动端强制居中显示 if (isMobile()) { modalContent.style.position = 'relative'; modalContent.style.top = 'auto'; modalContent.style.left = 'auto'; modalContent.style.right = 'auto'; modalContent.style.bottom = 'auto'; modalContent.style.margin = 'auto'; modalContent.style.width = 'calc(100% - 32px)'; modalContent.style.height = 'auto'; return; } const modalPosition = widgetInfo?.modal_position || defaultModalPosition; if (modalPosition === 'fixed') { // 固定居中模式不需要重新定位 return; } // 重新计算模态框位置(使用智能定位) positionModalFollow(modalContent); } }); // 初始化 function init() { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', fetchWidgetInfo); } else { fetchWidgetInfo(); } } // 页面卸载时清理 window.addEventListener('beforeunload', function () { if (widgetButton) { widgetButton.remove(); } if (widgetModal) { widgetModal.remove(); } if (customTriggerElement && customTriggerHandler) { customTriggerElement.removeEventListener('click', customTriggerHandler); customTriggerElement.removeAttribute('data-widget-trigger-attached'); } }); // 启动 init(); })();