feat(@vben/web-antd):迁移客服模块

This commit is contained in:
jawe
2025-10-30 15:39:08 +08:00
parent 38df83e281
commit 0ef91c1cad
69 changed files with 2259 additions and 40 deletions

View File

@@ -1,28 +1,132 @@
<script lang="ts" setup>
import { Page } from '@vben/common-ui';
import { defineOptions, onBeforeUnmount, onMounted, ref, watch } from 'vue';
import { Button } from 'ant-design-vue';
import { Page } from '@vben/common-ui';
import { useAccessStore } from '@vben/stores';
import { useWebSocket } from '@vueuse/core';
import message from 'ant-design-vue';
import { useMallKefuStore } from '#/store/mall/kefu';
import {
KeFuConversationList,
KeFuMessageList,
MemberInfo,
} from './components';
import { WebSocketMessageTypeConstants } from './components/tools/constants';
defineOptions({ name: 'KeFu' });
const accessStore = useAccessStore();
const kefuStore = useMallKefuStore(); // 客服缓存
// ======================= WebSocket start =======================
const url = `${`${import.meta.env.VITE_BASE_URL}/infra/ws`.replace(
'http',
'ws',
)}?token=${accessStore.refreshToken}`; // 使用 refreshToken() :WebSocket 无法方便的刷新访问令牌
const server = ref(url); // WebSocket 服务地址
/** 发起 WebSocket 连接 */
const { data, close, open } = useWebSocket(server.value, {
autoReconnect: true,
heartbeat: true,
});
/** 监听 WebSocket 数据 */
watch(
() => data.value,
(newData) => {
if (!newData) return;
try {
// 1. 收到心跳
if (newData === 'pong') return;
// 2.1 解析 type 消息类型
const jsonMessage = JSON.parse(newData);
const type = jsonMessage.type;
if (!type) {
message.error(`未知的消息类型:${newData}`);
return;
}
// 2.2 消息类型KEFU_MESSAGE_TYPE
if (type === WebSocketMessageTypeConstants.KEFU_MESSAGE_TYPE) {
const message = JSON.parse(jsonMessage.content);
// 刷新会话列表
kefuStore.updateConversation(message.conversationId);
// 刷新消息列表
keFuChatBoxRef.value?.refreshMessageList(message);
return;
}
// 2.3 消息类型KEFU_MESSAGE_ADMIN_READ
if (type === WebSocketMessageTypeConstants.KEFU_MESSAGE_ADMIN_READ) {
// 更新会话已读
const message = JSON.parse(jsonMessage.content);
kefuStore.updateConversationStatus(message.conversationId);
}
} catch (error) {
console.error(error);
}
},
{
immediate: false, // 不立即执行
},
);
// ======================= WebSocket end =======================
/** 加载指定会话的消息列表 */
const keFuChatBoxRef = ref<InstanceType<typeof KeFuMessageList>>();
const memberInfoRef = ref<InstanceType<typeof MemberInfo>>();
const handleChange = (conversation: KeFuConversationRespVO) => {
keFuChatBoxRef.value?.getNewMessageList(conversation);
memberInfoRef.value?.initHistory(conversation);
};
const keFuConversationRef = ref<InstanceType<typeof KeFuConversationList>>();
/** 初始化 */
onMounted(() => {
/** 加载会话列表 */
kefuStore.setConversationList().then(() => {
keFuConversationRef.value?.calculationLastMessageTime();
});
// 打开 websocket 连接
open();
});
/** 销毁 */
onBeforeUnmount(() => {
// 关闭 websocket 连接
close();
});
</script>
<template>
<Page>
<Button
danger
type="link"
target="_blank"
href="https://github.com/yudaocode/yudao-ui-admin-vue3"
>
该功能支持 Vue3 + element-plus 版本
</Button>
<br />
<Button
type="link"
target="_blank"
href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/mall/promotion/kefu/index"
>
可参考
https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/mall/promotion/kefu/index
代码pull request 贡献给我们
</Button>
<a-layout-content class="kefu-layout hrow">
<!-- 会话列表 -->
<KeFuConversationList ref="keFuConversationRef" @change="handleChange" />
<!-- 会话详情选中会话的消息列表 -->
<KeFuMessageList ref="keFuChatBoxRef" />
<!-- 会员信息选中会话的会员信息 -->
<MemberInfo ref="memberInfoRef" />
</a-layout-content>
</Page>
</template>
<style lang="scss">
.kefu-layout {
position: absolute;
top: 0;
left: 0;
flex: 1;
width: 100%;
height: 100%;
}
.hrow {
display: flex;
}
</style>