Files
Zhanglj 2ff1064de7 feat: add customer service chat, optimize case and homepage, fix api proxy
这一提交完成了多项功能升级与优化:
1.  新增全局浮动客服组件,集成腾讯云TRTC聊天能力并添加错误格式化工具
2.  新增图片资源并调整部分静态文件结构
3.  优化案例页面:重构筛选逻辑、新增空状态处理、添加加载更多功能
4.  优化首页布局:修复统计数字动画、调整移动端适配样式、优化轮播逻辑
5.  重构官方内容获取工具:添加缓存机制、优化媒体资源处理
6.  优化API代理配置,移除默认本地代理并新增环境变量配置
7.  新增页面数据恢复刷新逻辑,优化页面交互体验
8.  修复按钮组件冗余代码,调整头部导航移动端样式
2026-07-24 18:55:51 +08:00

152 lines
3.5 KiB
Vue

<script setup>
const route = useRoute()
const isOpen = ref(false)
const context = ref({ title: '在线客服', source: '' })
const isInteriorPage = computed(() => route.path !== '/')
function openFloatingChat() {
context.value = {
title: '在线客服',
source: isInteriorPage.value ? '' : ''
}
isOpen.value = true
}
function closeChat() {
isOpen.value = false
}
function onKeydown(event) {
if (event.key === 'Escape' && isOpen.value) closeChat()
}
onMounted(() => window.addEventListener('keydown', onKeydown))
onBeforeUnmount(() => window.removeEventListener('keydown', onKeydown))
</script>
<template>
<Teleport to="body">
<Transition name="consult-float">
<button
v-if="!isOpen"
class="consult-float-button"
:class="{ 'is-interior': isInteriorPage }"
type="button"
aria-label="打开在线客服"
title="在线客服"
@click="openFloatingChat"
>
<AppIcon name="service" />
<span>客服</span>
</button>
</Transition>
<Transition name="consult-panel">
<div v-if="isOpen" class="consult-chat-shell" :class="{ 'is-interior': isInteriorPage }">
<ClientOnly>
<ChatOfficialChatWindow :context="context" @close="closeChat" />
<template #fallback>
<section class="consult-client-loading">
<strong>正在打开在线客服...</strong>
</section>
</template>
</ClientOnly>
</div>
</Transition>
</Teleport>
</template>
<style scoped>
.consult-float-button {
position: fixed;
z-index: 46;
right: clamp(18px, 2.7vw, 12px);
bottom: clamp(18px, 25vw, 520px);
min-width: 88px;
height: 48px;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 0 16px;
border: 1px solid rgba(255, 255, 255, 0.28);
border-radius: 999px;
color: #fff;
background: var(--gradient-action);
box-shadow: 0 16px 34px rgba(35, 111, 231, 0.26);
font-size: 14px;
font-weight: 600;
transition:
transform 0.2s,
box-shadow 0.2s,
opacity 0.2s;
}
.consult-float-button :deep(.app-icon) {
width: 20px;
height: 20px;
}
.consult-float-button:hover {
transform: translateY(-3px);
box-shadow: 0 20px 40px rgba(35, 111, 231, 0.34);
}
.consult-float-button:active {
transform: translateY(1px) scale(0.98);
}
.consult-chat-shell {
position: fixed;
z-index: 90;
right: clamp(16px, 2.4vw, 36px);
bottom: clamp(16px, 2.4vw, 36px);
}
.consult-chat-shell.is-interior {
bottom: calc(clamp(16px, 2.4vw, 36px) + 58px);
}
.consult-client-loading {
width: min(420px, calc(100vw - 32px));
height: 180px;
display: grid;
place-items: center;
border-radius: 12px;
color: #2f4056;
background: #fff;
box-shadow: 0 24px 64px rgba(37, 94, 160, 0.18);
}
.consult-float-enter-active,
.consult-float-leave-active,
.consult-panel-enter-active,
.consult-panel-leave-active {
transition:
opacity 0.2s,
transform 0.24s cubic-bezier(0.22, 1, 0.36, 1);
}
.consult-float-enter-from,
.consult-float-leave-to {
opacity: 0;
transform: translateY(12px) scale(0.96);
}
.consult-panel-enter-from,
.consult-panel-leave-to {
opacity: 0;
transform: translateY(16px) scale(0.98);
}
@media (max-width: 640px) {
.consult-float-button {
right: 16px;
bottom: 16px;
min-width: 80px;
height: 44px;
}
.consult-float-button.is-interior {
bottom: 70px;
}
.consult-chat-shell,
.consult-chat-shell.is-interior {
right: 0;
bottom: 0;
left: 0;
}
}
</style>