@luckycode/vue-chat-button
Version:
Vue 3 chat button components with badge support, modal popup, fixed positioning, environment configuration, and customizable themes
1 lines • 19.7 kB
JavaScript
!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports,require("vue")):"function"==typeof define&&define.amd?define(["exports","vue"],n):n((e="undefined"!=typeof globalThis?globalThis:e||self).VueChatButton={},e.Vue)}(this,(function(e,n){"use strict";const t=e=>{const n="production"===e;return{isProduction:n,apiBaseUrl:n?"https://auth.szad.teehmoon.com":"https://auth.dev.szad.teehmoon.com",timeout:1e4,retryCount:3}},o={class:"chat-modal-header"},a={class:"chat-modal-title"},i={class:"chat-modal-content"},l=["src","title"],d={key:1,class:"chat-modal-placeholder"},c=n.defineComponent({__name:"ChatModal",props:{visible:{type:Boolean,default:!1},iframeUrl:{default:""},modalTitle:{default:"在线客服"},position:{default:"right"},width:{default:"400px"},height:{default:"600px"},closeOnOverlay:{type:Boolean,default:!0},closeOnEscape:{type:Boolean,default:!0}},emits:["update:visible","close","open"],setup(e,{expose:t,emit:c}){const r=e,s=c,h=n.ref(r.visible);n.watch((()=>r.visible),(e=>{h.value=e})),n.watch(h,(e=>{s("update:visible",e),s(e?"open":"close")}));const p=()=>{h.value=!1},m=()=>{r.closeOnOverlay&&p()},u=e=>{"Escape"===e.key&&h.value&&r.closeOnEscape&&(e.preventDefault(),p())};return t({open:()=>{h.value=!0},close:p,toggle:()=>{h.value=!h.value}}),n.onMounted((()=>{if("undefined"!=typeof document){const e="vue-chat-button-styles";if(!document.getElementById(e)){const n=document.createElement("style");n.id=e,n.textContent="\n .chat-modal-overlay {\n position: fixed;\n inset: 0;\n z-index: 9999;\n display: flex;\n align-items: center;\n justify-content: center;\n background: rgb(0 0 0 / 50%);\n }\n .chat-modal-container {\n display: flex;\n flex-direction: column;\n overflow: hidden;\n background: white;\n border-radius: 8px;\n box-shadow: 0 4px 20px rgb(0 0 0 / 15%);\n }\n .chat-modal-container.right {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: var(--modal-width, 400px);\n height: 100vh;\n border-radius: 0;\n animation: slideInRight 0.3s ease-out;\n }\n .chat-modal-container.center {\n width: var(--modal-width, 400px);\n max-width: 90vw;\n height: var(--modal-height, 600px);\n max-height: 90vh;\n }\n .chat-modal-container.fullscreen {\n position: fixed;\n inset: 0;\n width: 100vw;\n height: 100vh;\n border-radius: 0;\n }\n .chat-modal-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px 20px;\n background: #fafafa;\n border-bottom: 1px solid #e8e8e8;\n }\n .chat-modal-title {\n font-size: 16px;\n font-weight: 600;\n color: #333;\n }\n .chat-modal-close {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n color: #666;\n cursor: pointer;\n background: transparent;\n border: none;\n border-radius: 4px;\n transition: all 0.2s;\n }\n .chat-modal-close:hover {\n color: #333;\n background: #f0f0f0;\n }\n .chat-modal-close svg {\n width: 16px;\n height: 16px;\n }\n .chat-modal-content {\n flex: 1;\n overflow: hidden;\n }\n .chat-modal-iframe {\n width: 100%;\n height: 100%;\n border: none;\n }\n .chat-modal-placeholder {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n font-size: 14px;\n color: #999;\n }\n .modal-fade-enter-active,\n .modal-fade-leave-active {\n transition: opacity 0.3s ease;\n }\n .modal-fade-enter-from,\n .modal-fade-leave-to {\n opacity: 0;\n }\n @keyframes slideInRight {\n from {\n transform: translateX(100%);\n }\n to {\n transform: translateX(0);\n }\n }\n @media (width <= 768px) {\n .chat-modal-container.right {\n width: 100vw;\n }\n .chat-modal-container.center {\n width: 95vw;\n height: 80vh;\n }\n }\n ",document.head.appendChild(n)}}document.addEventListener("keydown",u,!0)})),n.onUnmounted((()=>{document.removeEventListener("keydown",u,!0)})),(e,t)=>(n.openBlock(),n.createBlock(n.Teleport,{to:"body"},[n.createVNode(n.Transition,{name:"modal-fade"},{default:n.withCtx((()=>[h.value?(n.openBlock(),n.createElementBlock("div",{key:0,class:"chat-modal-overlay",onClick:m},[n.createElementVNode("div",{class:n.normalizeClass(["chat-modal-container",e.position]),onClick:t[0]||(t[0]=n.withModifiers((()=>{}),["stop"]))},[n.createElementVNode("div",o,[n.createElementVNode("div",a,[n.renderSlot(e.$slots,"title",{},(()=>[n.createTextVNode(n.toDisplayString(e.modalTitle),1)]))]),n.createElementVNode("button",{class:"chat-modal-close",onClick:p},t[1]||(t[1]=[n.createElementVNode("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2"},[n.createElementVNode("line",{x1:"18",y1:"6",x2:"6",y2:"18"}),n.createElementVNode("line",{x1:"6",y1:"6",x2:"18",y2:"18"})],-1)]))]),n.createElementVNode("div",i,[e.iframeUrl?(n.openBlock(),n.createElementBlock("iframe",{key:0,src:e.iframeUrl,title:e.modalTitle,class:"chat-modal-iframe",frameborder:"0",allowfullscreen:""},null,8,l)):(n.openBlock(),n.createElementBlock("div",d,[n.renderSlot(e.$slots,"content",{},(()=>[t[2]||(t[2]=n.createElementVNode("p",null,"请设置 iframe URL",-1))]))]))])],2)])):n.createCommentVNode("",!0)])),_:3})]))}}),r={class:"chat-icon"},s=["src"],h={key:1,viewBox:"0 0 24 24",fill:"none"},p=n.defineComponent({__name:"ChatButtonWithBadge",props:{autoFetch:{type:Boolean,default:!0},fetchInterval:{default:3e4},enablePolling:{type:Boolean,default:!0},pollingInterval:{default:3e4},params:{default:void 0},iconUrl:{default:""},themeColor:{default:"#1890ff"},badgeColor:{default:"#ff4d4f"},environment:{default:"development"},modalUrl:{default:"https://admin.test.szad.teehmoon.com"},modalTitle:{default:"在线客服"},modalPosition:{default:"right"},modalWidth:{default:"400px"},modalHeight:{default:"600px"},closeOnOverlay:{type:Boolean,default:!0},closeOnEscape:{type:Boolean,default:!0},autoOpenModal:{type:Boolean,default:!0},isFixed:{type:Boolean,default:!0},fixedPosition:{default:"bottom-right"},fixedOffset:{default:()=>({x:20,y:60})},autoPosition:{type:Boolean,default:!0},mobilePosition:{default:"bottom-right"},desktopPosition:{default:"bottom-right"}},emits:["click","badgeUpdate","pollingStart","pollingStop","error"],setup(e,{expose:o,emit:a}){const i=e,l=a,d=n.ref(0),p=n.ref(!1),m=n.ref(null),u=n.ref(i.pollingInterval),f=n.ref(!1),x=n.computed((()=>({background:i.themeColor,boxShadow:`0 4px 12px ${i.themeColor}30`}))),g=n.computed((()=>({background:i.badgeColor,boxShadow:`0 2px 4px ${i.badgeColor}30`}))),v=n.computed((()=>{if(!i.isFixed)return{};const{x:e=20,y:n=20}=i.fixedOffset||{};let t=i.fixedPosition||"bottom-right";i.autoPosition&&(t=window.innerWidth>768?i.desktopPosition:i.mobilePosition);const o={position:"fixed",zIndex:"9999"};switch(t){case"bottom-right":default:o.right=e+"px",o.bottom=n+"px";break;case"bottom-left":o.left=e+"px",o.bottom=n+"px";break;case"top-right":o.right=e+"px",o.top=n+"px";break;case"top-left":o.left=e+"px",o.top=n+"px"}return o})),b=()=>{l("click"),(i.autoOpenModal||i.modalUrl)&&(f.value=!0)},y=()=>{},w=()=>{},k=async()=>{try{const e={method:"GET",headers:{"Content-Type":"application/json"},body:i.params},n=t(i.environment).apiBaseUrl+"/api/unread-count",o=await fetch(n,e);if(!o.ok)throw Error(`HTTP ${o.status}: ${o.statusText}`);{const e=(await o.json()).count||0;d.value=e,l("badgeUpdate",e)}}catch(e){l("error",e)}},B=()=>{p.value||(p.value=!0,l("pollingStart"),k(),m.value=setInterval(k,u.value))},C=()=>{m.value&&(clearInterval(m.value),m.value=null),p.value=!1,l("pollingStop")},E=e=>{p.value&&(C(),u.value=e,B())};return n.watch((()=>i.enablePolling),(e=>{e&&i.autoFetch?B():C()})),n.watch((()=>i.pollingInterval),(e=>{p.value&&E(e)})),n.onMounted((()=>{if("undefined"!=typeof document){const e="vue-chat-button-styles";if(!document.getElementById(e)){const n=document.createElement("style");n.id=e,n.textContent="\n .chat-button-with-badge {\n position: relative;\n display: inline-block;\n }\n .chat-button-with-badge.fixed-bottom-right {\n position: fixed;\n right: 20px;\n bottom: 20px;\n z-index: 9999;\n }\n @media (width <= 768px) {\n .chat-button-with-badge.fixed-bottom-right {\n right: 16px;\n bottom: 16px;\n }\n }\n .chat-button {\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 56px;\n height: 56px;\n cursor: pointer;\n border-radius: 12px;\n transition: all 0.3s ease;\n }\n .chat-button:hover {\n box-shadow: 0 6px 16px rgb(24 144 255 / 40%) !important;\n transform: translateY(-2px);\n }\n .chat-button:active {\n transform: translateY(0);\n }\n .chat-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 24px;\n height: 24px;\n }\n .chat-icon svg {\n width: 20px;\n height: 20px;\n }\n .custom-icon {\n width: 20px;\n height: 20px;\n object-fit: contain;\n }\n .badge {\n position: absolute;\n top: -6px;\n right: -6px;\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 20px;\n height: 20px;\n padding: 0 6px;\n font-size: 12px;\n font-weight: 600;\n color: white;\n border-radius: 10px;\n animation: pulse 2s infinite;\n }\n @keyframes pulse {\n 0% {\n transform: scale(1);\n }\n 50% {\n transform: scale(1.1);\n }\n 100% {\n transform: scale(1);\n }\n }\n ",document.head.appendChild(n)}}i.autoFetch&&i.enablePolling?B():i.autoFetch&&k()})),n.onUnmounted((()=>{C()})),o({fetchBadgeCount:k,setBadgeCount:e=>{d.value=e,l("badgeUpdate",e)},startPolling:B,stopPolling:C,updatePollingInterval:E,isPolling:()=>p.value,openModal:()=>{f.value=!0},closeModal:()=>{f.value=!1},toggleModal:()=>{f.value=!f.value}}),(e,t)=>(n.openBlock(),n.createElementBlock("div",{class:n.normalizeClass(["chat-button-with-badge",{"fixed-bottom-right":e.isFixed}]),style:n.normalizeStyle(v.value)},[n.createElementVNode("div",{class:"chat-button",style:n.normalizeStyle(x.value),onClick:b},[n.createElementVNode("div",r,[e.iconUrl?(n.openBlock(),n.createElementBlock("img",{key:0,src:e.iconUrl,alt:"chat icon",class:"custom-icon"},null,8,s)):(n.openBlock(),n.createElementBlock("svg",h,t[1]||(t[1]=[n.createElementVNode("path",{d:"M20 2H4C2.9 2 2 2.9 2 4V22L6 18H20C21.1 18 22 17.1 22 16V4C22 2.9 21.1 2 20 2ZM20 16H5.17L4 17.17V4H20V16Z",fill:"white"},null,-1),n.createElementVNode("circle",{cx:"7",cy:"9",r:"1",fill:"white"},null,-1),n.createElementVNode("circle",{cx:"12",cy:"9",r:"1",fill:"white"},null,-1),n.createElementVNode("circle",{cx:"17",cy:"9",r:"1",fill:"white"},null,-1)])))]),d.value>0?(n.openBlock(),n.createElementBlock("div",{key:0,class:"badge",style:n.normalizeStyle(g.value)},n.toDisplayString(d.value>99?"99+":d.value),5)):n.createCommentVNode("",!0)],4),n.createVNode(c,{visible:f.value,"onUpdate:visible":t[0]||(t[0]=e=>f.value=e),"iframe-url":e.modalUrl,"modal-title":e.modalTitle,position:e.modalPosition,width:e.modalWidth,height:e.modalHeight,"close-on-overlay":e.closeOnOverlay,"close-on-escape":e.closeOnEscape,onOpen:y,onClose:w},null,8,["visible","iframe-url","modal-title","position","width","height","close-on-overlay","close-on-escape"])],6))}}),m={class:"chat-icon"},u=["src"],f={key:1,viewBox:"0 0 24 24",fill:"none"},x=n.defineComponent({__name:"ChatButtonSimple",props:{iconUrl:{default:""},themeColor:{default:"#1890ff"},environment:{default:"development"}},emits:["click"],setup(e,{emit:t}){const o=e,a=t,i=n.computed((()=>({background:o.themeColor,boxShadow:`0 4px 12px ${o.themeColor}30`}))),l=()=>{a("click")};return n.onMounted((()=>{if("undefined"!=typeof document){const e="vue-chat-button-styles";if(!document.getElementById(e)){const n=document.createElement("style");n.id=e,n.textContent="\n .chat-button-simple {\n position: relative;\n display: inline-block;\n }\n .chat-button {\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 56px;\n height: 56px;\n cursor: pointer;\n border-radius: 12px;\n transition: all 0.3s ease;\n }\n .chat-button:hover {\n box-shadow: 0 6px 16px rgb(24 144 255 / 40%) !important;\n transform: translateY(-2px);\n }\n .chat-button:active {\n transform: translateY(0);\n }\n .chat-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 24px;\n height: 24px;\n }\n .chat-icon svg {\n width: 20px;\n height: 20px;\n }\n .custom-icon {\n width: 20px;\n height: 20px;\n object-fit: contain;\n }\n ",document.head.appendChild(n)}}})),(e,t)=>(n.openBlock(),n.createElementBlock("div",{class:"chat-button-simple",onClick:l},[n.createElementVNode("div",{class:"chat-button",style:n.normalizeStyle(i.value)},[n.createElementVNode("div",m,[e.iconUrl?(n.openBlock(),n.createElementBlock("img",{key:0,src:e.iconUrl,alt:"chat icon",class:"custom-icon"},null,8,u)):(n.openBlock(),n.createElementBlock("svg",f,t[0]||(t[0]=[n.createElementVNode("path",{d:"M20 2H4C2.9 2 2 2.9 2 4V22L6 18H20C21.1 18 22 17.1 22 16V4C22 2.9 21.1 2 20 2ZM20 16H5.17L4 17.17V4H20V16Z",fill:"white"},null,-1),n.createElementVNode("circle",{cx:"7",cy:"9",r:"1",fill:"white"},null,-1),n.createElementVNode("circle",{cx:"12",cy:"9",r:"1",fill:"white"},null,-1),n.createElementVNode("circle",{cx:"17",cy:"9",r:"1",fill:"white"},null,-1)])))])],4)]))}}),g={install(e){!function(){try{if("undefined"!=typeof document){const e="vue-chat-button-styles";if(!document.getElementById(e)){const n=document.createElement("style");n.id=e,n.textContent="\n.chat-button-simple {\n position: relative;\n display: inline-block;\n}\n\n.chat-button {\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 56px;\n height: 56px;\n cursor: pointer;\n border-radius: 12px;\n transition: all 0.3s ease;\n}\n\n.chat-button:hover {\n box-shadow: 0 6px 16px rgb(24 144 255 / 40%) !important;\n transform: translateY(-2px);\n}\n\n.chat-button:active {\n transform: translateY(0);\n}\n\n.chat-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 24px;\n height: 24px;\n}\n\n.chat-icon svg {\n width: 20px;\n height: 20px;\n}\n\n.custom-icon {\n width: 20px;\n height: 20px;\n object-fit: contain;\n}\n\n.chat-button-with-badge {\n position: relative;\n display: inline-block;\n}\n\n.chat-button-with-badge.fixed-bottom-right {\n position: fixed;\n right: 20px;\n bottom: 20px;\n z-index: 9999;\n}\n\n@media (width <= 768px) {\n .chat-button-with-badge.fixed-bottom-right {\n right: 16px;\n bottom: 16px;\n }\n}\n\n.badge {\n position: absolute;\n top: -6px;\n right: -6px;\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 20px;\n height: 20px;\n padding: 0 6px;\n font-size: 12px;\n font-weight: 600;\n color: white;\n border-radius: 10px;\n animation: pulse 2s infinite;\n}\n\n@keyframes pulse {\n 0% {\n transform: scale(1);\n }\n\n 50% {\n transform: scale(1.1);\n }\n\n 100% {\n transform: scale(1);\n }\n}\n\n.chat-modal-overlay {\n position: fixed;\n inset: 0;\n z-index: 9999;\n display: flex;\n align-items: center;\n justify-content: center;\n background: rgb(0 0 0 / 50%);\n}\n\n.chat-modal-container {\n display: flex;\n flex-direction: column;\n overflow: hidden;\n background: white;\n border-radius: 8px;\n box-shadow: 0 4px 20px rgb(0 0 0 / 15%);\n}\n\n.chat-modal-container.right {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: var(--modal-width, 400px);\n height: 100vh;\n border-radius: 0;\n animation: slideInRight 0.3s ease-out;\n}\n\n.chat-modal-container.center {\n width: var(--modal-width, 400px);\n max-width: 90vw;\n height: var(--modal-height, 600px);\n max-height: 90vh;\n}\n\n.chat-modal-container.fullscreen {\n position: fixed;\n inset: 0;\n width: 100vw;\n height: 100vh;\n border-radius: 0;\n}\n\n.chat-modal-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px 20px;\n background: #fafafa;\n border-bottom: 1px solid #e8e8e8;\n}\n\n.chat-modal-title {\n font-size: 16px;\n font-weight: 600;\n color: #333;\n}\n\n.chat-modal-close {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n color: #666;\n cursor: pointer;\n background: transparent;\n border: none;\n border-radius: 4px;\n transition: all 0.2s;\n}\n\n.chat-modal-close:hover {\n color: #333;\n background: #f0f0f0;\n}\n\n.chat-modal-close svg {\n width: 16px;\n height: 16px;\n}\n\n.chat-modal-content {\n flex: 1;\n overflow: hidden;\n}\n\n.chat-modal-iframe {\n width: 100%;\n height: 100%;\n border: none;\n}\n\n.chat-modal-placeholder {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n font-size: 14px;\n color: #999;\n}\n\n.modal-fade-enter-active,\n.modal-fade-leave-active {\n transition: opacity 0.3s ease;\n}\n\n.modal-fade-enter-from,\n.modal-fade-leave-to {\n opacity: 0;\n}\n\n@keyframes slideInRight {\n from {\n transform: translateX(100%);\n }\n\n to {\n transform: translateX(0);\n }\n}\n\n@media (width <= 768px) {\n .chat-modal-container.right {\n width: 100vw;\n }\n\n .chat-modal-container.center {\n width: 95vw;\n height: 80vh;\n }\n}\n",document.head.appendChild(n)}}}catch(e){}}(),e.component("ChatButtonWithBadge",p),e.component("ChatButtonSimple",x),e.component("ChatModal",c),e._context&&e._context.provides&&e.provide("vue-chat-button-styles-injected",!0)}};e.ChatButtonSimple=x,e.ChatButtonWithBadge=p,e.ChatModal=c,e.default=g,e.detectEnvironment=()=>{if("undefined"!=typeof window){const e=window.location.hostname,n=window.location.protocol;return"localhost"===e||"127.0.0.1"===e||e.includes(".local")||e.includes(".dev")||"file:"===n||"dev"===new URLSearchParams(window.location.search).get("env")?"development":"production"}return"development"},e.getEnvironmentConfig=t,Object.defineProperties(e,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}));