feat: [mall] kefu 客服模块迁移 代码风格统一

This commit is contained in:
jawe
2025-10-30 16:27:44 +08:00
parent 0ef91c1cad
commit f331f46ff9
8 changed files with 79 additions and 76 deletions

View File

@@ -27,24 +27,24 @@ const collapse = ref(false); // 折叠菜单
/** 计算消息最后发送时间距离现在过去了多久 */ /** 计算消息最后发送时间距离现在过去了多久 */
const lastMessageTimeMap = ref<Map<number, string>>(new Map<number, string>()); const lastMessageTimeMap = ref<Map<number, string>>(new Map<number, string>());
const calculationLastMessageTime = () => { function calculationLastMessageTime() {
kefuStore.getConversationList?.forEach((item) => { kefuStore.getConversationList?.forEach((item) => {
lastMessageTimeMap.value.set( lastMessageTimeMap.value.set(
item.id, item.id,
formatPast(item.lastMessageTime, 'YYYY-MM-DD'), formatPast(item.lastMessageTime, 'YYYY-MM-DD'),
); );
}); });
}; }
defineExpose({ calculationLastMessageTime }); defineExpose({ calculationLastMessageTime });
const openRightMessage = (item: MallKefuConversationApi.Conversation) => { function openRightMessage(item: MallKefuConversationApi.Conversation) {
// 同一个会话则不处理 // 同一个会话则不处理
if (activeConversationId.value === item.id) { if (activeConversationId.value === item.id) {
return; return;
} }
activeConversationId.value = item.id; activeConversationId.value = item.id;
emits('change', item); emits('change', item);
}; }
/** 获得消息类型 */ /** 获得消息类型 */
const getConversationDisplayText = computed( const getConversationDisplayText = computed(
@@ -88,10 +88,10 @@ const rightClickConversation = ref<MallKefuConversationApi.Conversation>(
); // 右键选中的会话对象 ); // 右键选中的会话对象
/** 打开右键菜单 */ /** 打开右键菜单 */
const rightClick = ( function rightClick(
mouseEvent: PointerEvent, mouseEvent: PointerEvent,
item: MallKefuConversationApi.Conversation, item: MallKefuConversationApi.Conversation,
) => { ) {
rightClickConversation.value = item; rightClickConversation.value = item;
// 显示右键菜单 // 显示右键菜单
showRightMenu.value = true; showRightMenu.value = true;
@@ -101,14 +101,14 @@ const rightClick = (
? `${mouseEvent.clientX - 80}px` ? `${mouseEvent.clientX - 80}px`
: `${mouseEvent.clientX - 210}px`, : `${mouseEvent.clientX - 210}px`,
}; };
}; }
/** 关闭右键菜单 */ /** 关闭右键菜单 */
const closeRightMenu = () => { function closeRightMenu() {
showRightMenu.value = false; showRightMenu.value = false;
}; }
/** 置顶会话 */ /** 置顶会话 */
const updateConversationPinned = async (adminPinned: boolean) => { async function updateConversationPinned(adminPinned: boolean) {
// 1. 会话置顶/取消置顶 // 1. 会话置顶/取消置顶
await KeFuConversationApi.updateConversationPinned({ await KeFuConversationApi.updateConversationPinned({
id: rightClickConversation.value.id, id: rightClickConversation.value.id,
@@ -118,17 +118,17 @@ const updateConversationPinned = async (adminPinned: boolean) => {
// 2. 关闭右键菜单,更新会话列表 // 2. 关闭右键菜单,更新会话列表
closeRightMenu(); closeRightMenu();
await kefuStore.updateConversation(rightClickConversation.value.id); await kefuStore.updateConversation(rightClickConversation.value.id);
}; }
/** 删除会话 */ /** 删除会话 */
const deleteConversation = async () => { async function deleteConversation() {
// 1. 删除会话 // 1. 删除会话
await message.confirm('您确定要删除该会话吗?'); await message.confirm('您确定要删除该会话吗?');
await KeFuConversationApi.deleteConversation(rightClickConversation.value.id); await KeFuConversationApi.deleteConversation(rightClickConversation.value.id);
// 2. 关闭右键菜单,更新会话列表 // 2. 关闭右键菜单,更新会话列表
closeRightMenu(); closeRightMenu();
kefuStore.deleteConversation(rightClickConversation.value.id); kefuStore.deleteConversation(rightClickConversation.value.id);
}; }
/** 监听右键菜单的显示状态,添加点击事件监听器 */ /** 监听右键菜单的显示状态,添加点击事件监听器 */
watch(showRightMenu, (val) => { watch(showRightMenu, (val) => {

View File

@@ -53,7 +53,7 @@ const getMessageContent = computed(
() => (item: any) => jsonParse(item.content), () => (item: any) => jsonParse(item.content),
); );
/** 获得消息列表 */ /** 获得消息列表 */
const getMessageList = async () => { async function getMessageList() {
const res = await KeFuMessageApi.getKeFuMessageList(queryParams); const res = await KeFuMessageApi.getKeFuMessageList(queryParams);
if (isEmpty(res)) { if (isEmpty(res)) {
// 当返回的是空列表说明没有消息或者已经查询完了历史消息 // 当返回的是空列表说明没有消息或者已经查询完了历史消息
@@ -72,15 +72,15 @@ const getMessageList = async () => {
messageList.value = res; messageList.value = res;
} }
refreshContent.value = true; refreshContent.value = true;
}; }
/** 添加消息 */ /** 添加消息 */
const pushMessage = (message: any) => { function pushMessage(message: any) {
if (messageList.value.some((val) => val.id === message.id)) { if (messageList.value.some((val) => val.id === message.id)) {
return; return;
} }
messageList.value.push(message); messageList.value.push(message);
}; }
/** 按照时间倒序,获取消息列表 */ /** 按照时间倒序,获取消息列表 */
const getMessageList0 = computed(() => { const getMessageList0 = computed(() => {
@@ -91,7 +91,7 @@ const getMessageList0 = computed(() => {
}); });
/** 刷新消息列表 */ /** 刷新消息列表 */
const refreshMessageList = async (message?: any) => { async function refreshMessageList(message?: any) {
if (!conversation.value) { if (!conversation.value) {
return; return;
} }
@@ -114,10 +114,10 @@ const refreshMessageList = async (message?: any) => {
// 滚动到最新消息处 // 滚动到最新消息处
await handleToNewMessage(); await handleToNewMessage();
} }
}; }
/** 获得新会话的消息列表, 点击切换时读取缓存然后异步获取新消息merge 下; */ /** 获得新会话的消息列表, 点击切换时读取缓存然后异步获取新消息merge 下; */
const getNewMessageList = async (val: MallKefuMessageApi.Message) => { async function getNewMessageList(val: MallKefuMessageApi.Message) {
// 1. 缓存当前会话消息列表 // 1. 缓存当前会话消息列表
kefuStore.saveMessageList(conversation.value.id, messageList.value); kefuStore.saveMessageList(conversation.value.id, messageList.value);
// 2.1 会话切换,重置相关参数 // 2.1 会话切换,重置相关参数
@@ -132,19 +132,23 @@ const getNewMessageList = async (val: MallKefuMessageApi.Message) => {
queryParams.createTime = undefined; queryParams.createTime = undefined;
// 3. 获取消息 // 3. 获取消息
await refreshMessageList(); await refreshMessageList();
}; }
defineExpose({ getNewMessageList, refreshMessageList }); defineExpose({ getNewMessageList, refreshMessageList });
const showKeFuMessageList = computed(() => !isEmpty(conversation.value)); // 是否显示聊天区域 // 是否显示聊天区域
function showKeFuMessageList() {
return !isEmpty(conversation.value);
}
const skipGetMessageList = ref(false); // 跳过消息获取 const skipGetMessageList = ref(false); // 跳过消息获取
/** 处理表情选择 */ /** 处理表情选择 */
const handleEmojiSelect = (item: Emoji) => { function handleEmojiSelect(item: Emoji) {
message.value += item.name; message.value += item.name;
}; }
/** 处理图片发送 */ /** 处理图片发送 */
const handleSendPicture = async (picUrl: string) => { async function handleSendPicture(picUrl: string) {
// 组织发送消息 // 组织发送消息
const msg = { const msg = {
conversationId: conversation.value.id, conversationId: conversation.value.id,
@@ -152,10 +156,10 @@ const handleSendPicture = async (picUrl: string) => {
content: JSON.stringify({ picUrl }), content: JSON.stringify({ picUrl }),
}; };
await sendMessage(msg); await sendMessage(msg);
}; }
/** 发送文本消息 */ /** 发送文本消息 */
const handleSendMessage = async (event: any) => { async function handleSendMessage(event: any) {
// shift 不发送 // shift 不发送
if (event.shiftKey) { if (event.shiftKey) {
return; return;
@@ -173,10 +177,10 @@ const handleSendMessage = async (event: any) => {
content: JSON.stringify({ text: message.value }), content: JSON.stringify({ text: message.value }),
}; };
await sendMessage(msg); await sendMessage(msg);
}; }
/** 真正发送消息 【共用】*/ /** 真正发送消息 【共用】*/
const sendMessage = async (msg: MallKefuMessageApi.MessageSend) => { async function sendMessage(msg: MallKefuMessageApi.MessageSend) {
// 发送消息 // 发送消息
await KeFuMessageApi.sendKeFuMessage(msg); await KeFuMessageApi.sendKeFuMessage(msg);
message.value = ''; message.value = '';
@@ -184,14 +188,14 @@ const sendMessage = async (msg: MallKefuMessageApi.MessageSend) => {
await refreshMessageList(); await refreshMessageList();
// 更新会话缓存 // 更新会话缓存
await kefuStore.updateConversation(conversation.value.id); await kefuStore.updateConversation(conversation.value.id);
}; }
/** 滚动到底部 */ /** 滚动到底部 */
const innerRef = ref<HTMLDivElement>(); const innerRef = ref<HTMLDivElement>();
const scrollbarRef = ref<HTMLElement | null>(null); const scrollbarRef = ref<HTMLElement | null>(null);
const { y } = useScroll(scrollbarRef); const { y } = useScroll(scrollbarRef);
const scrollToBottom = async () => { async function scrollToBottom() {
if (!scrollbarRef.value) return; if (!scrollbarRef.value) return;
// 1. 首次加载时滚动到最新消息,如果加载的是历史消息则不滚动 // 1. 首次加载时滚动到最新消息,如果加载的是历史消息则不滚动
if (loadHistory.value) { if (loadHistory.value) {
@@ -206,13 +210,13 @@ const scrollToBottom = async () => {
showNewMessageTip.value = false; showNewMessageTip.value = false;
// 2.2 消息已读 // 2.2 消息已读
await KeFuMessageApi.updateKeFuMessageReadStatus(conversation.value.id); await KeFuMessageApi.updateKeFuMessageReadStatus(conversation.value.id);
}; }
/** 查看新消息 */ /** 查看新消息 */
const handleToNewMessage = async () => { async function handleToNewMessage() {
loadHistory.value = false; loadHistory.value = false;
await scrollToBottom(); await scrollToBottom();
}; }
const loadHistory = ref(false); // 加载历史消息 const loadHistory = ref(false); // 加载历史消息
/** 处理消息列表滚动事件(debounce 限流) */ /** 处理消息列表滚动事件(debounce 限流) */
@@ -233,7 +237,7 @@ const handleScroll = useDebounceFn((state: UseScrollReturn) => {
} }
}, 200); }, 200);
/** 加载历史消息 */ /** 加载历史消息 */
const handleOldMessage = async () => { async function handleOldMessage() {
// 记录已有页面高度 // 记录已有页面高度
const oldPageHeight = innerRef.value?.clientHeight; const oldPageHeight = innerRef.value?.clientHeight;
if (!oldPageHeight) { if (!oldPageHeight) {
@@ -247,7 +251,7 @@ const handleOldMessage = async () => {
scrollbarRef.value.scrollHeight - scrollbarRef.value.scrollHeight -
innerRef.value!.clientHeight - innerRef.value!.clientHeight -
oldPageHeight; oldPageHeight;
}; }
/** /**
* 是否显示时间 * 是否显示时间
@@ -255,8 +259,7 @@ const handleOldMessage = async () => {
* @param {*} item - 数据 * @param {*} item - 数据
* @param {*} index - 索引 * @param {*} index - 索引
*/ */
const showTime = computed( function showTime(item: MallKefuMessageApi.Message, index: number) {
() => (item: MallKefuMessageApi.Message, index: number) => {
if (unref(messageList.value)[index + 1]) { if (unref(messageList.value)[index + 1]) {
const dateString = dayjs( const dateString = dayjs(
unref(messageList.value)[index + 1].createTime, unref(messageList.value)[index + 1].createTime,
@@ -264,8 +267,7 @@ const showTime = computed(
return dateString !== dayjs(unref(item).createTime).fromNow(); return dateString !== dayjs(unref(item).createTime).fromNow();
} }
return false; return false;
}, }
);
</script> </script>
<template> <template>
@@ -417,7 +419,7 @@ const showTime = computed(
</a-layout> </a-layout>
<a-layout v-else class="kefu"> <a-layout v-else class="kefu">
<a-layout-content> <a-layout-content>
<Empty description="请选择左侧的一个会话后开始" class="mt-[50px]"/> <Empty description="请选择左侧的一个会话后开始" class="mt-[50px]" />
</a-layout-content> </a-layout-content>
</a-layout> </a-layout>
</template> </template>

View File

@@ -8,7 +8,7 @@ import { isEmpty } from '@vben/utils';
// import { debounce } from 'lodash-es' // import { debounce } from 'lodash-es'
import { useDebounceFn } from '@vueuse/core'; import { useDebounceFn } from '@vueuse/core';
import { Card, Empty,message } from 'ant-design-vue'; import { Card, Empty, message } from 'ant-design-vue';
import * as UserApi from '#/api/member/user'; import * as UserApi from '#/api/member/user';
import * as WalletApi from '#/api/pay/wallet/balance'; import * as WalletApi from '#/api/pay/wallet/balance';
@@ -29,14 +29,14 @@ const productBrowsingHistoryRef =
ref<InstanceType<typeof ProductBrowsingHistory>>(); ref<InstanceType<typeof ProductBrowsingHistory>>();
const orderBrowsingHistoryRef = const orderBrowsingHistoryRef =
ref<InstanceType<typeof OrderBrowsingHistory>>(); ref<InstanceType<typeof OrderBrowsingHistory>>();
const handleClick = async (tab: string) => { async function handleClick(tab: string) {
activeTab.value = tab; activeTab.value = tab;
await nextTick(); await nextTick();
await getHistoryList(); await getHistoryList();
}; }
/** 获得历史数据 */ /** 获得历史数据 */
const getHistoryList = async () => { async function getHistoryList() {
switch (activeTab.value) { switch (activeTab.value) {
case '交易订单': { case '交易订单': {
await orderBrowsingHistoryRef.value?.getHistoryList(conversation.value); await orderBrowsingHistoryRef.value?.getHistoryList(conversation.value);
@@ -55,10 +55,10 @@ const getHistoryList = async () => {
break; break;
} }
} }
}; }
/** 加载下一页数据 */ /** 加载下一页数据 */
const loadMore = async () => { async function loadMore() {
switch (activeTab.value) { switch (activeTab.value) {
case '交易订单': { case '交易订单': {
await orderBrowsingHistoryRef.value?.loadMore(); await orderBrowsingHistoryRef.value?.loadMore();
@@ -75,18 +75,18 @@ const loadMore = async () => {
break; break;
} }
} }
}; }
/** 浏览历史初始化 */ /** 浏览历史初始化 */
const conversation = ref<MallKefuConversationApi.Conversation>( const conversation = ref<MallKefuConversationApi.Conversation>(
{} as MallKefuConversationApi.Conversation, {} as MallKefuConversationApi.Conversation,
); // 用户会话 ); // 用户会话
const initHistory = async (val: MallKefuConversationApi.Conversation) => { async function initHistory(val: MallKefuConversationApi.Conversation) {
activeTab.value = '会员信息'; activeTab.value = '会员信息';
conversation.value = val; conversation.value = val;
await nextTick(); await nextTick();
await getHistoryList(); await getHistoryList();
}; }
defineExpose({ initHistory }); defineExpose({ initHistory });
/** 处理消息列表滚动事件(debounce 限流) */ /** 处理消息列表滚动事件(debounce 限流) */
@@ -106,7 +106,8 @@ const WALLET_INIT_DATA = {
totalRecharge: 0, totalRecharge: 0,
} as WalletApi.WalletVO; // 钱包初始化数据 } as WalletApi.WalletVO; // 钱包初始化数据
const wallet = ref<WalletApi.WalletVO>(WALLET_INIT_DATA); // 钱包信息 const wallet = ref<WalletApi.WalletVO>(WALLET_INIT_DATA); // 钱包信息
const getUserWallet = async () => {
async function getUserWallet() {
if (!conversation.value.userId) { if (!conversation.value.userId) {
wallet.value = WALLET_INIT_DATA; wallet.value = WALLET_INIT_DATA;
return; return;
@@ -114,12 +115,12 @@ const getUserWallet = async () => {
wallet.value = wallet.value =
(await WalletApi.getWallet({ userId: conversation.value.userId })) || (await WalletApi.getWallet({ userId: conversation.value.userId })) ||
WALLET_INIT_DATA; WALLET_INIT_DATA;
}; }
/** 获得用户 */ /** 获得用户 */
const loading = ref(true); // 加载中 const loading = ref(true); // 加载中
const user = ref<UserApi.UserVO>({} as UserApi.UserVO); const user = ref<UserApi.UserVO>({} as UserApi.UserVO);
const getUserData = async () => { async function getUserData() {
loading.value = true; loading.value = true;
try { try {
const res = await UserApi.getUser(conversation.value.userId); const res = await UserApi.getUser(conversation.value.userId);
@@ -132,7 +133,7 @@ const getUserData = async () => {
} finally { } finally {
loading.value = false; loading.value = false;
} }
}; }
</script> </script>
<template> <template>

View File

@@ -24,15 +24,15 @@ const skipGetMessageList = computed(() => {
}); // 跳过消息获取 }); // 跳过消息获取
/** 获得浏览记录 */ /** 获得浏览记录 */
const getHistoryList = async (val: MallKefuConversationApi.Conversation) => { async function getHistoryList(val: MallKefuConversationApi.Conversation) {
queryParams.userId = val.userId; queryParams.userId = val.userId;
const res = await getOrderPage(queryParams); const res = await getOrderPage(queryParams);
total.value = res.total; total.value = res.total;
list.value = res.list; list.value = res.list;
}; }
/** 加载下一页数据 */ /** 加载下一页数据 */
const loadMore = async () => { async function loadMore() {
if (skipGetMessageList.value) { if (skipGetMessageList.value) {
return; return;
} }
@@ -41,7 +41,7 @@ const loadMore = async () => {
total.value = res.total; total.value = res.total;
// 使用展开运算符替代 concat 方法 // 使用展开运算符替代 concat 方法
list.value = [...list.value, ...res.list]; list.value = [...list.value, ...res.list];
}; }
defineExpose({ getHistoryList, loadMore }); defineExpose({ getHistoryList, loadMore });
</script> </script>

View File

@@ -26,15 +26,15 @@ const skipGetMessageList = computed(() => {
}); // 跳过消息获取 }); // 跳过消息获取
/** 获得浏览记录 */ /** 获得浏览记录 */
const getHistoryList = async (val: MallKefuConversationApi.Conversation) => { async function getHistoryList(val: MallKefuConversationApi.Conversation) {
queryParams.userId = val.userId; queryParams.userId = val.userId;
const res = await getBrowseHistoryPage(queryParams); const res = await getBrowseHistoryPage(queryParams);
total.value = res.total; total.value = res.total;
list.value = res.list; list.value = res.list;
}; }
/** 加载下一页数据 */ /** 加载下一页数据 */
const loadMore = async () => { async function loadMore() {
if (skipGetMessageList.value) { if (skipGetMessageList.value) {
return; return;
} }
@@ -43,7 +43,7 @@ const loadMore = async () => {
total.value = res.total; total.value = res.total;
// 使用展开运算符替代 concat 方法 // 使用展开运算符替代 concat 方法
list.value = [...list.value, ...res.list]; list.value = [...list.value, ...res.list];
}; }
defineExpose({ getHistoryList, loadMore }); defineExpose({ getHistoryList, loadMore });
</script> </script>

View File

@@ -24,9 +24,9 @@ const getMessageContent = computed(() =>
); );
/** 查看订单详情 */ /** 查看订单详情 */
const openDetail = (id: number) => { function openDetail(id: number) {
push({ name: 'TradeOrderDetail', params: { id } }); push({ name: 'TradeOrderDetail', params: { id } });
}; }
/** /**
* 格式化订单状态的颜色 * 格式化订单状态的颜色

View File

@@ -37,9 +37,9 @@ defineProps({
const { push } = useRouter(); const { push } = useRouter();
/** 查看商品详情 */ /** 查看商品详情 */
const openDetail = (spuId: number) => { function openDetail(spuId: number) {
push({ name: 'ProductSpuDetail', params: { id: spuId } }); push({ name: 'ProductSpuDetail', params: { id: spuId } });
}; }
</script> </script>
<template> <template>
@@ -89,10 +89,10 @@ const openDetail = (spuId: number) => {
align-items: center; align-items: center;
width: 100%; width: 100%;
padding: 10px; padding: 10px;
margin-bottom: 10px;
background-color: rgb(128 128 128 / 30%); background-color: rgb(128 128 128 / 30%);
border: 1px solid var(--el-border-color); border: 1px solid var(--el-border-color);
border-radius: 8px; border-radius: 8px;
margin-bottom: 10px;
&-left { &-left {
width: 70px; width: 70px;

View File

@@ -19,10 +19,10 @@ const emits = defineEmits<{
const { getEmojiList } = useEmoji(); const { getEmojiList } = useEmoji();
const emojiList = computed(() => getEmojiList()); const emojiList = computed(() => getEmojiList());
const handleSelect = (item: Emoji) => { function handleSelect(item: Emoji) {
// 整个 emoji 数据传递出去,方便以后输入框直接显示表情 // 整个 emoji 数据传递出去,方便以后输入框直接显示表情
emits('selectEmoji', item); emits('selectEmoji', item);
}; }
</script> </script>
<template> <template>