添加通知弹窗、音效,具有防抖

This commit is contained in:
peak 2025-06-12 18:43:40 +08:00
parent d8f71b14cb
commit 8114f48e70
9 changed files with 9346 additions and 7592 deletions

View File

@ -5,7 +5,8 @@ VITE_DEV=true
# 请求路径 # 请求路径
VITE_BASE_URL='https://zysc.fjptzykj.com' VITE_BASE_URL='https://zysc.fjptzykj.com'
# VITE_BASE_URL='http://192.168.1.12:6127' # VITE_BASE_URL='http://192.168.10.75:6127'
# VITE_BASE_URL='http://127.0.0.1:6127'
# 文件上传类型server - 后端上传, client - 前端直连上传,仅支持 S3 服务 # 文件上传类型server - 后端上传, client - 前端直连上传,仅支持 S3 服务
VITE_UPLOAD_TYPE=server VITE_UPLOAD_TYPE=server

View File

@ -62,7 +62,7 @@
"editor.defaultFormatter": "esbenp.prettier-vscode" "editor.defaultFormatter": "esbenp.prettier-vscode"
}, },
"[typescript]": { "[typescript]": {
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint" "editor.defaultFormatter": "vscode.typescript-language-features"
}, },
"[typescriptreact]": { "[typescriptreact]": {
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint" "editor.defaultFormatter": "rvest.vs-code-prettier-eslint"

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,9 @@ import { useAppStore } from '@/store/modules/app'
import { useDesign } from '@/hooks/web/useDesign' import { useDesign } from '@/hooks/web/useDesign'
import { CACHE_KEY, useCache } from '@/hooks/web/useCache' import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
import routerSearch from '@/components/RouterSearch/index.vue' import routerSearch from '@/components/RouterSearch/index.vue'
import EventBus from '@/utils/eventBus'
import { debounce } from 'lodash'
import { ElNotification } from 'element-plus'
defineOptions({ name: 'APP' }) defineOptions({ name: 'APP' })
const { getPrefixCls } = useDesign() const { getPrefixCls } = useDesign()
@ -23,6 +25,17 @@ const setDefaultTheme = () => {
appStore.setIsDark(isDarkTheme) appStore.setIsDark(isDarkTheme)
} }
setDefaultTheme() setDefaultTheme()
const debounceNotify = debounce((msg) => {
alert(msg)
ElNotification.info({
title: "新消息",
message: msg,
duration: 3000,
})
}, 600);
onMounted(() => {
EventBus.on('show-notification', debounceNotify);
})
</script> </script>
<template> <template>
<ConfigGlobal :size="currentSize"> <ConfigGlobal :size="currentSize">

View File

@ -5,6 +5,9 @@ import { Backtop } from '@/components/Backtop'
import { Setting } from '@/layout/components/Setting' import { Setting } from '@/layout/components/Setting'
import { useRenderLayout } from './components/useRenderLayout' import { useRenderLayout } from './components/useRenderLayout'
import { useDesign } from '@/hooks/web/useDesign' import { useDesign } from '@/hooks/web/useDesign'
import EventBus from '@/utils/eventBus'
import { debounce } from 'lodash'
import { ElNotification } from 'element-plus'
const { getPrefixCls } = useDesign() const { getPrefixCls } = useDesign()
@ -42,7 +45,14 @@ const renderLayout = () => {
break break
} }
} }
const debounceNotify = debounce((msg) => {
ElNotification.info({
title: "新消息",
message: msg,
duration: 3000,
})
}, 600);
EventBus.on('notification', debounceNotify);
export default defineComponent({ export default defineComponent({
name: 'Layout', name: 'Layout',
setup() { setup() {

View File

@ -0,0 +1,2 @@
import mitt from 'mitt';
export default mitt();

View File

@ -7,13 +7,12 @@
<div style="width:100%;height:68px;background-color:#3c80ff;"> <div style="width:100%;height:68px;background-color:#3c80ff;">
<el-row> <el-row>
<el-col :span="6"> <el-col :span="6">
<el-input @input="findNameInput" v-model="findName" <el-input @input="findNameInput" v-model="findName" style="width: 80%;margin-top: 20px;margin-left:23px;"
style="width: 80%;margin-top: 20px;margin-left:23px;" :suffix-icon="Search" />
:suffix-icon="Search"/>
</el-col> </el-col>
<el-col :span="1"> <el-col :span="1">
<el-avatar style=" margin-top: 15px;" :src="pic"/> <el-avatar style=" margin-top: 15px;" :src="pic" />
<!-- <span style="display: flex; margin-top: 15px;"> <!-- <span style="display: flex; margin-top: 15px;">
<span style="margin-left:5px;margin-top: 9px;">{{name}}</span> <span style="margin-left:5px;margin-top: 9px;">{{name}}</span>
@ -27,17 +26,15 @@
<span style="display: flex; margin-top: 15px;"> <span style="display: flex; margin-top: 15px;">
<!-- <el-avatar :src="pic"/> --> <!-- <el-avatar :src="pic"/> -->
<span style="margin-left:5px;margin-top: 9px;">{{name}}</span> <span style="margin-left:5px;margin-top: 9px;">{{ name }}</span>
<el-switch <el-switch style="margin-top: 4px;--el-switch-on-color: #13ce66; --el-switch-off-color: #b6bac1;"
style="margin-top: 4px;--el-switch-on-color: #13ce66; --el-switch-off-color: #b6bac1;" v-model="lineStatus" class="ml-2" width="60" inline-prompt active-text="在线" inactive-text="下线"
v-model="lineStatus" class="ml-2" width="60" inline-prompt active-text="在线" @change="handleSwitchChange" />
inactive-text="下线" @change="handleSwitchChange"/>
</span> </span>
</el-col> </el-col>
<el-col :span="4"> <el-col :span="4">
<el-button @click="out" size="small" round <el-button @click="out" size="small" round style="margin-top:23px;margin-left:72%">退出
style="margin-top:23px;margin-left:72%">退出
</el-button> </el-button>
</el-col> </el-col>
@ -65,14 +62,14 @@
<!-- 会话列表 --> <!-- 会话列表 -->
<el-col :span="6"> <el-col :span="6">
<ContentWrap> <ContentWrap>
<KeFuConversationList ref="keFuConversationRef" @change="handleChange"/> <KeFuConversationList ref="keFuConversationRef" @change="handleChange" />
</ContentWrap> </ContentWrap>
</el-col> </el-col>
<!-- 会话详情选中会话的消息列表 --> <!-- 会话详情选中会话的消息列表 -->
<el-col :span="12"> <el-col :span="12">
<KeFuMessageList ref="keFuChatBoxRef" @change="getConversationList"/> <KeFuMessageList ref="keFuChatBoxRef" @change="getConversationList" />
</el-col> </el-col>
@ -83,29 +80,29 @@
<div style="height: 522px ;"> <div style="height: 522px ;">
<div> <div>
<span style="display: flex;"> <span style="display: flex;">
<el-avatar style="border: 1px solid #f8f9ee;" :src="user.avatar"/> <el-avatar style="border: 1px solid #f8f9ee;" :src="user.avatar" />
<span style="margin-left:5px;margin-top: 9px;">{{user.nickname}}</span> <span style="margin-left:5px;margin-top: 9px;">{{ user.nickname }}</span>
</span> </span>
</div> </div>
<el-divider style="border-color: #f5f5f5;margin-top: 15px;margin-bottom:15px;"/> <el-divider style="border-color: #f5f5f5;margin-top: 15px;margin-bottom:15px;" />
<div> <div>
<span style="color: #5d5d59;font-size: 13px ;">手机号</span><span <span style="color: #5d5d59;font-size: 13px ;">手机号</span><span
style="margin-left: 47px;font-size: 14px ;">{{user.mobile}}</span> style="margin-left: 47px;font-size: 14px ;">{{ user.mobile }}</span>
</div> </div>
<div style="margin-top: 5px;"> <div style="margin-top: 5px;">
<span style="color: #5d5d59;font-size: 13px ;">分组</span><span <span style="color: #5d5d59;font-size: 13px ;">分组</span><span
style="margin-left: 60px;font-size: 14px ;">{{user.groupName}}</span> style="margin-left: 60px;font-size: 14px ;">{{ user.groupName }}</span>
</div> </div>
<div style="margin-top: 5px;"> <div style="margin-top: 5px;">
<span style="color: #5d5d59;font-size: 13px ;">用户标签</span><span <span style="color: #5d5d59;font-size: 13px ;">用户标签</span><span
style="margin-left: 33px;font-size: 14px ;">客户</span> style="margin-left: 33px;font-size: 14px ;">客户</span>
</div> </div>
<el-divider style="border-color: #f5f5f5;margin-top: 15px;margin-bottom:15px;"/> <el-divider style="border-color: #f5f5f5;margin-top: 15px;margin-bottom:15px;" />
<div> <div>
<span style="color: #5d5d59;font-size: 13px ;">用户等级</span><span <span style="color: #5d5d59;font-size: 13px ;">用户等级</span><span
style="margin-left: 35px;font-size: 14px ;">{{user.levelName}}</span> style="margin-left: 35px;font-size: 14px ;">{{ user.levelName }}</span>
</div> </div>
<div style="margin-top: 5px;"> <div style="margin-top: 5px;">
<span style="color: #5d5d59;font-size: 13px ;">推荐人</span><span <span style="color: #5d5d59;font-size: 13px ;">推荐人</span><span
@ -117,7 +114,7 @@
</div> --> </div> -->
<div style="margin-top: 5px;"> <div style="margin-top: 5px;">
<span style="color: #5d5d59;font-size: 13px ;">积分</span><span <span style="color: #5d5d59;font-size: 13px ;">积分</span><span
style="margin-left: 60px;font-size: 14px ;">{{user.point}}</span> style="margin-left: 60px;font-size: 14px ;">{{ user.point }}</span>
</div> </div>
<div style="margin-top: 5px;"> <div style="margin-top: 5px;">
<span style="color: #5d5d59;font-size: 13px ;">推广员</span><span <span style="color: #5d5d59;font-size: 13px ;">推广员</span><span
@ -125,9 +122,9 @@
</div> </div>
<div style="margin-top: 5px;"> <div style="margin-top: 5px;">
<span style="color: #5d5d59;font-size: 13px ;">生日</span><span <span style="color: #5d5d59;font-size: 13px ;">生日</span><span
style="margin-left: 60px;font-size: 14px ;">{{user.birthday}}</span> style="margin-left: 60px;font-size: 14px ;">{{ user.birthday }}</span>
</div> </div>
<el-divider style="border-color: #f5f5f5;margin-top: 15px;margin-bottom:15px;"/> <el-divider style="border-color: #f5f5f5;margin-top: 15px;margin-bottom:15px;" />
</div> </div>
</ContentWrap> </ContentWrap>
@ -136,11 +133,11 @@
</ContentWrap> </ContentWrap>
<ContentWrap v-show="chick == '2'"> <ContentWrap v-show="chick == '2'">
<MemberBrowsingHistory ref="memberBrowsingHistoryRef"/> <MemberBrowsingHistory ref="memberBrowsingHistoryRef" />
</ContentWrap> </ContentWrap>
<ContentWrap v-show="chick == '3'"> <ContentWrap v-show="chick == '3'">
<MemberBrowsingHistorys ref="memberBrowsingHistorysRef"/> <MemberBrowsingHistorys ref="memberBrowsingHistorysRef" />
</ContentWrap> </ContentWrap>
</el-col> </el-col>
@ -150,54 +147,57 @@
<script lang="ts" setup> <script lang="ts" setup>
import { import {
KeFuConversationList, KeFuConversationList,
KeFuMessageList, KeFuMessageList,
MemberBrowsingHistorys, MemberBrowsingHistorys,
MemberBrowsingHistory MemberBrowsingHistory
} from './components' } from './components'
import {WebSocketMessageTypeConstants} from './components/tools/constants' import { WebSocketMessageTypeConstants } from './components/tools/constants'
import { KeFuMessageApi, KeFuMessageRespVO } from '@/api/mall/promotion/kefu/message' import { KeFuMessageApi, KeFuMessageRespVO } from '@/api/mall/promotion/kefu/message'
import {KeFuConversationRespVO} from '@/api/mall/promotion/kefu/conversation' import { KeFuConversationRespVO } from '@/api/mall/promotion/kefu/conversation'
import {getRefreshToken, getAccessToken} from '@/utils/auth' import { getRefreshToken, getAccessToken } from '@/utils/auth'
import {useWebSocket} from '@vueuse/core' import { useWebSocket } from '@vueuse/core'
import {Search} from '@element-plus/icons-vue' import { Search } from '@element-plus/icons-vue'
import * as UserApi from '@/api/member/user' import * as UserApi from '@/api/member/user'
import {SupportStaffApi, SupportStaffVO} from '@/api/mall/promotion/supportstaff' import { SupportStaffApi, SupportStaffVO } from '@/api/mall/promotion/supportstaff'
import {string} from 'vue-types' import { string } from 'vue-types'
import EventBus from '@/utils/eventBus';
import bgm from './xm3463.mp3'
import type { TabsPaneContext } from 'element-plus' import type { TabsPaneContext } from 'element-plus'
const findName = ref('') import { ElNotification } from 'element-plus'
const findName = ref('')
const audio = new Audio(bgm)
defineOptions({ name: 'KeFu' })
defineOptions({name: 'KeFu'}) const lineStatus = ref(true)
const lineStatus = ref(true) const clickUser = ref(1)
const clickUser = ref(1)
const message = useMessage() // const message = useMessage() //
const params = new URLSearchParams(window.location.search); const params = new URLSearchParams(window.location.search);
const name = params.get('name'); const name = params.get('name');
const pic = params.get('pic'); const pic = params.get('pic');
const kefuId = params.get('id') const kefuId = params.get('id')
const conversations = ref<KeFuConversationRespVO[]>([]) const conversations = ref<KeFuConversationRespVO[]>([])
// const userInfoRef = ref<InstanceType<typeof UserInfo>>() // const userInfoRef = ref<InstanceType<typeof UserInfo>>()
const user = ref<UserApi.UserVO>({} as UserApi.UserVO) const user = ref<UserApi.UserVO>({} as UserApi.UserVO)
const userId = ref(0) const userId = ref(0)
const stat = ref(false) const stat = ref(false)
// ======================= WebSocket start ======================= // ======================= WebSocket start =======================
const server = ref( const server = ref(
(import.meta.env.VITE_BASE_URL + '/infra/ws').replace('http', 'ws') + (import.meta.env.VITE_BASE_URL + '/infra/ws').replace('http', 'ws') +
'?token=' + getRefreshToken() '?token=' + getAccessToken()
// getAccessToken() // getAccessToken()
// 使 getRefreshToken() 使 getAccessToken() WebSocket 便访 // 使 getRefreshToken() 使 getAccessToken() WebSocket 便访
) // WebSocket ) // WebSocket
const handleSwitchChange = async (value) => { const handleSwitchChange = async (value) => {
console.log('11111:', value) console.log('11111:', value)
let a = 0; let a = 0;
@ -211,19 +211,19 @@ import type { TabsPaneContext } from 'element-plus'
message.success('已下线') message.success('已下线')
} }
} }
const shangxian = async () => { const shangxian = async () => {
await SupportStaffApi.updateLineStatus(kefuId, 1) await SupportStaffApi.updateLineStatus(kefuId, 1)
} }
const xiaxian = async () => { const xiaxian = async () => {
await SupportStaffApi.updateLineStatus(kefuId, 2) await SupportStaffApi.updateLineStatus(kefuId, 2)
} }
let a = 0; let a = 0;
const {status, data, send, open, close} = useWebSocket(server.value, { const { status, data, send, open, close } = useWebSocket(server.value, {
onConnected: function (ws) { onConnected: function (ws) {
shangxian(); //线 shangxian(); //线
console.log('websocket 连接成功!', ws); console.log('websocket 连接成功!', ws);
@ -241,36 +241,26 @@ import type { TabsPaneContext } from 'element-plus'
// } // }
// }, // },
onMessage: function (ws, event) { onMessage: function (ws, event) {
// console.log(' WebSocket :', event.data);
a = a + 1; a = a + 1;
if (a == 2) { if (a == 2) {
getConversationList() getConversationList()
if(userId.value != 0){ if (userId.value != 0) {
getBySenderIdStat() getBySenderIdStat()
if(stat.value){ if (stat.value) {
keFuChatBoxRef.value?.refreshMessageList() keFuChatBoxRef.value?.refreshMessageList()
} }
} }
a = 0; a = 0;
} }
}, },
autoReconnect: true, // autoReconnect: true, //
heartbeat: true heartbeat: true
}); });
// const { data, close, open } = useWebSocket(server.value, {
// autoReconnect: true,
// heartbeat: true
// })
/** 监听 WebSocket 数据 */
watchEffect(() => { /** 监听 WebSocket 数据 */
console.log('连接服务器得到消息:', data.value) watchEffect(() => {
if (!data.value) { if (!data.value) {
return return
} }
@ -292,6 +282,23 @@ import type { TabsPaneContext } from 'element-plus'
// 2.2 KEFU_MESSAGE_TYPE // 2.2 KEFU_MESSAGE_TYPE
if (type === WebSocketMessageTypeConstants.KEFU_MESSAGE_TYPE) { if (type === WebSocketMessageTypeConstants.KEFU_MESSAGE_TYPE) {
console.log('来自用户发送的消息:', JSON.parse(jsonMessage.content)) console.log('来自用户发送的消息:', JSON.parse(jsonMessage.content))
const message = JSON.parse(jsonMessage.content)
if (message.senderType == 1) {
audio.play()
EventBus.emit('notification', message.content)
// Notification.requestPermission().then(permission => {
// if (permission === 'granted') {
// console.log('');
// }
// });
// if (Notification.permission === 'granted') {
// console.warn('');
// new Notification(":"+message.content);
// } else {
// console.warn('');
// }
}
// //
// TODO @puhui999 update // TODO @puhui999 update
getConversationList() getConversationList()
@ -308,23 +315,23 @@ import type { TabsPaneContext } from 'element-plus'
} catch (error) { } catch (error) {
console.error(error) console.error(error)
} }
}) })
// ======================= WebSocket end ======================= // ======================= WebSocket end =======================
/** 加载会话列表 */ /** 加载会话列表 */
const keFuConversationRef = ref<InstanceType<typeof KeFuConversationList>>() const keFuConversationRef = ref<InstanceType<typeof KeFuConversationList>>()
const getConversationList = () => { const getConversationList = () => {
keFuConversationRef.value?.getConversationList(findName.value) keFuConversationRef.value?.getConversationList(findName.value)
} }
/** 加载指定会话的消息列表 */ /** 加载指定会话的消息列表 */
const keFuChatBoxRef = ref<InstanceType<typeof KeFuMessageList>>() const keFuChatBoxRef = ref<InstanceType<typeof KeFuMessageList>>()
const memberBrowsingHistoryRef = ref<InstanceType<typeof MemberBrowsingHistory>>() const memberBrowsingHistoryRef = ref<InstanceType<typeof MemberBrowsingHistory>>()
const memberBrowsingHistorysRef = ref<InstanceType<typeof MemberBrowsingHistorys>>() const memberBrowsingHistorysRef = ref<InstanceType<typeof MemberBrowsingHistorys>>()
const handleChange = async (conversation: KeFuConversationRespVO) => { const handleChange = async (conversation: KeFuConversationRespVO) => {
conversations.value = conversation conversations.value = conversation
// chick.value = '1' // chick.value = '1'
clickUser.value = 2 clickUser.value = 2
@ -333,52 +340,52 @@ import type { TabsPaneContext } from 'element-plus'
keFuChatBoxRef.value?.getNewMessageList(conversation) keFuChatBoxRef.value?.getNewMessageList(conversation)
memberBrowsingHistoryRef.value?.initHistory(conversation) memberBrowsingHistoryRef.value?.initHistory(conversation)
memberBrowsingHistorysRef.value?.initHistory(conversation) memberBrowsingHistorysRef.value?.initHistory(conversation)
} }
const out = async () => { const out = async () => {
// await SupportStaffApi.updateLineStatus(kefuId, 2) // await SupportStaffApi.updateLineStatus(kefuId, 2)
window.close(); window.close();
// window.location.href = '/kefu/support-staff'; // window.location.href = '/kefu/support-staff';
} }
const getBySenderIdStat = async () => { const getBySenderIdStat = async () => {
stat.value = await KeFuMessageApi.getBySenderIdStat(userId.value) stat.value = await KeFuMessageApi.getBySenderIdStat(userId.value)
} }
const chick = ref('1') const chick = ref('1')
const userInfo = async () => { const userInfo = async () => {
chick.value = '1' chick.value = '1'
if(clickUser.value == 2){ if (clickUser.value == 2) {
user.value = await UserApi.getUserInfo(userId.value) user.value = await UserApi.getUserInfo(userId.value)
} }
} }
const zuoji = () => { const zuoji = () => {
chick.value = '2' chick.value = '2'
// keFuChatBoxRef.value?.getNewMessageList(conversations.value) // keFuChatBoxRef.value?.getNewMessageList(conversations.value)
memberBrowsingHistoryRef.value?.initHistory(conversations.value) memberBrowsingHistoryRef.value?.initHistory(conversations.value)
} }
const jiaoyi = () => { const jiaoyi = () => {
chick.value = '3' chick.value = '3'
// keFuChatBoxRef.value?.getNewMessageList(conversations.value) // keFuChatBoxRef.value?.getNewMessageList(conversations.value)
// memberBrowsingHistoryRef.value?.initHistory(conversations.value) // memberBrowsingHistoryRef.value?.initHistory(conversations.value)
} }
const findNameInput = () => { const findNameInput = () => {
keFuConversationRef.value?.getConversationList(findName.value) keFuConversationRef.value?.getConversationList(findName.value)
} }
/** 初始化 */ /** 初始化 */
onMounted(() => { onMounted(() => {
getConversationList() getConversationList()
// websocket // websocket
open() open()
console.log('WebSocket 已初始化'); console.log('WebSocket 已初始化');
}) })
// /** */ // /** */
// onBeforeUnmount(() => { // onBeforeUnmount(() => {
// // websocket // // websocket
// close() // close()
@ -387,32 +394,32 @@ import type { TabsPaneContext } from 'element-plus'
</script> </script>
<style lang="scss"> <style lang="scss">
.kefu { .kefu {
height: calc(100vh - 165px); height: calc(100vh - 165px);
overflow: auto; overflow: auto;
/* 确保内容可滚动 */ /* 确保内容可滚动 */
} }
/* 定义滚动条样式 */ /* 定义滚动条样式 */
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 10px; width: 10px;
height: 6px; height: 6px;
} }
/* 定义滚动条轨道 内阴影+圆角 */ /* 定义滚动条轨道 内阴影+圆角 */
::-webkit-scrollbar-track { ::-webkit-scrollbar-track {
box-shadow: inset 0 0 0 rgba(240, 240, 240, 0.5); box-shadow: inset 0 0 0 rgba(240, 240, 240, 0.5);
border-radius: 10px; border-radius: 10px;
background-color: #fff; background-color: #fff;
} }
/* 定义滑块 内阴影+圆角 */ /* 定义滑块 内阴影+圆角 */
::-webkit-scrollbar-thumb { ::-webkit-scrollbar-thumb {
border-radius: 10px; border-radius: 10px;
box-shadow: inset 0 0 0 rgba(240, 240, 240, 0.5); box-shadow: inset 0 0 0 rgba(240, 240, 240, 0.5);
background-color: rgba(240, 240, 240, 0.5); background-color: rgba(240, 240, 240, 0.5);
} }
</style> </style>

View File

@ -169,6 +169,8 @@ import { SupportStaffApi, SupportStaffVO } from '@/api/mall/promotion/supportsta
import SupportStaffForm from './SupportStaffForm.vue' import SupportStaffForm from './SupportStaffForm.vue'
import { setStaffToken} from '@/utils/auth' import { setStaffToken} from '@/utils/auth'
import { createImageViewer } from '@/components/ImageViewer' import { createImageViewer } from '@/components/ImageViewer'
import { useRouter } from 'vue-router'
const router = useRouter()
/** 客服人员 列表 */ /** 客服人员 列表 */
defineOptions({ name: 'SupportStaff' }) defineOptions({ name: 'SupportStaff' })
@ -247,8 +249,10 @@ const handleDelete = async (id: number) => {
/** 客服进入工作台 */ /** 客服进入工作台 */
const handleEnterConsole = async (id: number,name: string,pic: string) => { const handleEnterConsole = async (id: number,name: string,pic: string) => {
setStaffToken(id); setStaffToken(id);
const url = `${window.location.origin}/kefu/kefu?id=${encodeURIComponent(id)}&name=${encodeURIComponent(name)}&pic=${encodeURIComponent(pic)}`; const url = `${window.location.origin}/kefu/chat?id=${encodeURIComponent(id)}&name=${encodeURIComponent(name)}&pic=${encodeURIComponent(pic)}`;
window.open(url, '_blank'); window.open(url, '_blank');
// const url = `/kefu/chat?id=${encodeURIComponent(id)}&name=${encodeURIComponent(name)}&pic=${encodeURIComponent(pic)}`;
// router.push(url);
} }
/** 导出按钮操作 */ /** 导出按钮操作 */
const handleExport = async () => { const handleExport = async () => {