179 lines
7.1 KiB
Vue
179 lines
7.1 KiB
Vue
<script setup lang="ts">
|
|
import { ref, watch } from 'vue'
|
|
import { useRouter } from 'vitepress'
|
|
import { MessageCircle, Phone, BookOpen, Users, X } from 'lucide-vue-next'
|
|
|
|
const router = useRouter()
|
|
const isOpen = ref(false)
|
|
const showChatModal = ref(false)
|
|
const chatMessages = ref<{ text?: string; isQrcode?: boolean; isTyping?: boolean }[]>([])
|
|
const isTypingComplete = ref(false)
|
|
|
|
const menuItems = [
|
|
{ icon: MessageCircle, label: '在线咨询', sub: '实时答疑', href: '#consult' },
|
|
{ icon: Phone, label: '课程报名', sub: '18322616529', href: 'tel:18322616529' },
|
|
{ icon: BookOpen, label: '书籍购买', sub: '官方商城', href: '/books' },
|
|
{ icon: Users, label: '商务合作', sub: '企业/机构', href: '/contact' },
|
|
]
|
|
|
|
let timers: ReturnType<typeof setTimeout>[] = []
|
|
|
|
watch(showChatModal, (val) => {
|
|
if (!val) {
|
|
chatMessages.value = []
|
|
isTypingComplete.value = false
|
|
timers.forEach(clearTimeout)
|
|
timers = []
|
|
return
|
|
}
|
|
|
|
timers.push(setTimeout(() => { chatMessages.value = [{ isTyping: true }] }, 500))
|
|
timers.push(setTimeout(() => {
|
|
chatMessages.value = [{
|
|
text: '你好同学,正在接入人工客服,或者方便的话,您先留个手机号联系方式,或加我微信咱们再具体沟通?'
|
|
}]
|
|
}, 2500))
|
|
timers.push(setTimeout(() => {
|
|
chatMessages.value = [...chatMessages.value, { isTyping: true }]
|
|
}, 3000))
|
|
timers.push(setTimeout(() => {
|
|
chatMessages.value = [...chatMessages.value.filter(m => !m.isTyping), { isQrcode: true }]
|
|
isTypingComplete.value = true
|
|
}, 4000))
|
|
})
|
|
|
|
const handleMenuItemClick = (href: string) => {
|
|
if (href === '#consult') {
|
|
showChatModal.value = true
|
|
isOpen.value = false
|
|
} else if (href.startsWith('tel:')) {
|
|
window.location.href = href
|
|
} else {
|
|
router.go(href)
|
|
isOpen.value = false
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="fixed bottom-6 right-6 z-50 flex flex-col items-end">
|
|
<div
|
|
:class="[
|
|
'mb-4 flex flex-col gap-3 transition-all duration-300 origin-bottom-right',
|
|
isOpen
|
|
? 'opacity-100 scale-100 translate-y-0'
|
|
: 'opacity-0 scale-90 translate-y-10 pointer-events-none'
|
|
]"
|
|
>
|
|
<button
|
|
v-for="(item, index) in menuItems"
|
|
:key="index"
|
|
@click="handleMenuItemClick(item.href)"
|
|
class="flex items-center gap-3 bg-white/90 backdrop-blur-lg border border-white/20 p-3 pr-4 rounded-2xl shadow-lg cursor-pointer hover:bg-white hover:scale-105 transition-all group"
|
|
>
|
|
<div class="w-10 h-10 rounded-full bg-blue-50 text-apple-blue flex items-center justify-center group-hover:bg-apple-blue group-hover:text-white transition-colors flex-shrink-0">
|
|
<component :is="item.icon" :size="20" />
|
|
</div>
|
|
<div class="text-left min-w-[80px]">
|
|
<div class="text-sm font-bold text-gray-800">{{ item.label }}</div>
|
|
<div class="text-xs text-gray-400">{{ item.sub }}</div>
|
|
</div>
|
|
</button>
|
|
</div>
|
|
|
|
<button
|
|
@click="isOpen = !isOpen"
|
|
:class="[
|
|
'w-16 h-16 rounded-full shadow-2xl flex items-center justify-center transition-all duration-300 hover:scale-110 active:scale-95',
|
|
isOpen
|
|
? 'bg-gray-800 rotate-90'
|
|
: 'bg-apple-blue hover:bg-apple-blue-hover'
|
|
]"
|
|
:aria-label="isOpen ? '关闭菜单' : '打开客服菜单'"
|
|
>
|
|
<X v-if="isOpen" class="text-white" :size="28" />
|
|
<MessageCircle v-else class="text-white fill-current" :size="28" />
|
|
</button>
|
|
|
|
<div
|
|
v-if="showChatModal"
|
|
class="fixed inset-0 z-[9999] flex items-center justify-center p-4"
|
|
>
|
|
<div
|
|
class="absolute inset-0 bg-black/60 backdrop-blur-sm"
|
|
@click="showChatModal = false"
|
|
/>
|
|
|
|
<div class="relative bg-white rounded-3xl shadow-2xl w-full max-w-md max-h-[600px] flex flex-col animate-slide-up">
|
|
<div class="flex items-center justify-between p-4 border-b bg-gradient-to-r from-apple-blue to-blue-600 rounded-t-3xl">
|
|
<div class="flex items-center gap-3">
|
|
<div class="w-10 h-10 bg-white/20 rounded-full flex items-center justify-center">
|
|
<MessageCircle :size="20" class="text-white" />
|
|
</div>
|
|
<div>
|
|
<h3 class="font-bold text-white">在线客服</h3>
|
|
<p class="text-xs text-white/80">在线</p>
|
|
</div>
|
|
</div>
|
|
<button
|
|
@click="showChatModal = false"
|
|
class="p-2 rounded-full hover:bg-white/20 transition-colors"
|
|
>
|
|
<X :size="20" class="text-white" />
|
|
</button>
|
|
</div>
|
|
|
|
<div class="flex-1 overflow-y-auto p-4 space-y-4 bg-gray-50">
|
|
<div v-for="(msg, index) in chatMessages" :key="index">
|
|
<div v-if="msg.isTyping" class="flex items-start gap-2">
|
|
<div class="w-8 h-8 bg-apple-blue rounded-full flex items-center justify-center flex-shrink-0">
|
|
<MessageCircle :size="16" class="text-white" />
|
|
</div>
|
|
<div class="bg-white rounded-2xl rounded-tl-none px-4 py-3 shadow-sm">
|
|
<div class="flex gap-1">
|
|
<span class="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style="animation-delay: 0ms;"></span>
|
|
<span class="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style="animation-delay: 150ms;"></span>
|
|
<span class="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style="animation-delay: 300ms;"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-else-if="msg.isQrcode" class="flex items-start gap-2">
|
|
<div class="w-8 h-8 bg-apple-blue rounded-full flex items-center justify-center flex-shrink-0">
|
|
<MessageCircle :size="16" class="text-white" />
|
|
</div>
|
|
<div class="bg-white rounded-2xl rounded-tl-none p-4 shadow-sm">
|
|
<img
|
|
src="https://duiwaifengxiang-1304851894.cos.ap-beijing.myqcloud.com/gwgongxue/code.jpg"
|
|
alt="客服二维码"
|
|
class="w-48 h-48 rounded-lg"
|
|
/>
|
|
<p class="text-xs text-gray-500 text-center mt-2">扫码添加微信客服</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-else class="flex items-start gap-2 animate-slide-up">
|
|
<div class="w-8 h-8 bg-apple-blue rounded-full flex items-center justify-center flex-shrink-0">
|
|
<MessageCircle :size="16" class="text-white" />
|
|
</div>
|
|
<div class="bg-white rounded-2xl rounded-tl-none px-4 py-3 shadow-sm max-w-[80%]">
|
|
<p class="text-sm text-gray-800 leading-relaxed">{{ msg.text }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="p-4 border-t bg-white rounded-b-3xl">
|
|
<div class="bg-gray-50 rounded-xl p-3 text-center">
|
|
<p class="text-xs text-gray-500">
|
|
{{ isTypingComplete
|
|
? '工作时间:周一至周日 9:00-21:00'
|
|
: '客服正在输入中…' }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|