widget.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. /**
  2. * AI-CS 访客聊天小窗插件
  3. * 可嵌入到任何网站中,提供客服聊天功能
  4. *
  5. * 使用方法:
  6. * <script src="https://your-domain.com/widget.js"></script>
  7. * <script>
  8. * AICSWidget.init({
  9. * apiUrl: 'https://your-api-domain.com',
  10. * position: 'bottom-right' // 可选:'bottom-right' | 'bottom-left'
  11. * });
  12. * </script>
  13. */
  14. (function() {
  15. 'use strict';
  16. // 配置
  17. const defaultConfig = {
  18. apiUrl: 'http://localhost:8080',
  19. position: 'bottom-right', // 'bottom-right' | 'bottom-left'
  20. theme: 'default'
  21. };
  22. let config = { ...defaultConfig };
  23. let widgetContainer = null;
  24. let isInitialized = false;
  25. /**
  26. * 创建浮动按钮
  27. */
  28. function createFloatingButton() {
  29. const button = document.createElement('button');
  30. button.className = 'ai-cs-widget-button';
  31. button.setAttribute('aria-label', '打开客服聊天');
  32. button.innerHTML = `
  33. <svg class="ai-cs-widget-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  34. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"></path>
  35. </svg>
  36. `;
  37. // 样式
  38. const position = config.position === 'bottom-left' ? 'left: 1rem;' : 'right: 1rem;';
  39. button.style.cssText = `
  40. position: fixed;
  41. bottom: 1rem;
  42. ${position}
  43. width: 3.5rem;
  44. height: 3.5rem;
  45. border-radius: 9999px;
  46. background-color: #3b82f6;
  47. color: white;
  48. border: none;
  49. cursor: pointer;
  50. box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
  51. z-index: 9999;
  52. display: flex;
  53. align-items: center;
  54. justify-content: center;
  55. transition: all 0.3s;
  56. `;
  57. button.onmouseover = function() {
  58. this.style.transform = 'scale(1.1)';
  59. this.style.boxShadow = '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)';
  60. };
  61. button.onmouseout = function() {
  62. this.style.transform = 'scale(1)';
  63. this.style.boxShadow = '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)';
  64. };
  65. button.onclick = function() {
  66. toggleWidget();
  67. };
  68. return button;
  69. }
  70. /**
  71. * 创建聊天窗口 iframe
  72. */
  73. function createChatWindow() {
  74. const iframe = document.createElement('iframe');
  75. iframe.id = 'ai-cs-widget-iframe';
  76. iframe.src = config.chatPageUrl || `${config.apiUrl.replace('/api', '')}/chat`;
  77. iframe.style.cssText = `
  78. position: fixed;
  79. bottom: 5rem;
  80. ${config.position === 'bottom-left' ? 'left: 1rem;' : 'right: 1rem;'}
  81. width: 400px;
  82. max-width: calc(100vw - 2rem);
  83. height: 600px;
  84. max-height: calc(100vh - 6rem);
  85. border: none;
  86. border-radius: 0.5rem;
  87. box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
  88. z-index: 9998;
  89. display: none;
  90. background: white;
  91. `;
  92. return iframe;
  93. }
  94. /**
  95. * 切换聊天窗口显示/隐藏
  96. */
  97. function toggleWidget() {
  98. const iframe = document.getElementById('ai-cs-widget-iframe');
  99. if (iframe) {
  100. const isVisible = iframe.style.display !== 'none';
  101. iframe.style.display = isVisible ? 'none' : 'block';
  102. // 更新按钮图标
  103. const button = document.querySelector('.ai-cs-widget-button');
  104. if (button) {
  105. const icon = button.querySelector('.ai-cs-widget-icon');
  106. if (icon) {
  107. if (isVisible) {
  108. // 显示聊天图标
  109. icon.innerHTML = '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"></path>';
  110. } else {
  111. // 显示关闭图标
  112. icon.innerHTML = '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>';
  113. }
  114. }
  115. }
  116. }
  117. }
  118. /**
  119. * 初始化插件
  120. */
  121. function init(userConfig) {
  122. if (isInitialized) {
  123. console.warn('AI-CS Widget 已经初始化');
  124. return;
  125. }
  126. // 合并配置
  127. config = { ...defaultConfig, ...userConfig };
  128. // 创建容器
  129. widgetContainer = document.createElement('div');
  130. widgetContainer.id = 'ai-cs-widget-container';
  131. widgetContainer.style.cssText = 'position: fixed; bottom: 0; z-index: 9999;';
  132. // 创建浮动按钮
  133. const button = createFloatingButton();
  134. widgetContainer.appendChild(button);
  135. // 创建聊天窗口
  136. const iframe = createChatWindow();
  137. widgetContainer.appendChild(iframe);
  138. // 添加到页面
  139. document.body.appendChild(widgetContainer);
  140. isInitialized = true;
  141. console.log('AI-CS Widget 初始化成功');
  142. }
  143. /**
  144. * 销毁插件
  145. */
  146. function destroy() {
  147. if (widgetContainer && widgetContainer.parentNode) {
  148. widgetContainer.parentNode.removeChild(widgetContainer);
  149. widgetContainer = null;
  150. isInitialized = false;
  151. console.log('AI-CS Widget 已销毁');
  152. }
  153. }
  154. // 暴露全局 API
  155. window.AICSWidget = {
  156. init: init,
  157. destroy: destroy,
  158. toggle: toggleWidget
  159. };
  160. // 如果 DOM 已加载,自动初始化(使用默认配置)
  161. if (document.readyState === 'loading') {
  162. document.addEventListener('DOMContentLoaded', function() {
  163. // 不自动初始化,需要用户手动调用 AICSWidget.init()
  164. });
  165. }
  166. })();