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

View File

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

View File

@@ -8,7 +8,7 @@ import { isEmpty } from '@vben/utils';
// import { debounce } from 'lodash-es'
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 WalletApi from '#/api/pay/wallet/balance';
@@ -29,14 +29,14 @@ const productBrowsingHistoryRef =
ref<InstanceType<typeof ProductBrowsingHistory>>();
const orderBrowsingHistoryRef =
ref<InstanceType<typeof OrderBrowsingHistory>>();
const handleClick = async (tab: string) => {
async function handleClick(tab: string) {
activeTab.value = tab;
await nextTick();
await getHistoryList();
};
}
/** 获得历史数据 */
const getHistoryList = async () => {
async function getHistoryList() {
switch (activeTab.value) {
case '交易订单': {
await orderBrowsingHistoryRef.value?.getHistoryList(conversation.value);
@@ -55,10 +55,10 @@ const getHistoryList = async () => {
break;
}
}
};
}
/** 加载下一页数据 */
const loadMore = async () => {
async function loadMore() {
switch (activeTab.value) {
case '交易订单': {
await orderBrowsingHistoryRef.value?.loadMore();
@@ -75,18 +75,18 @@ const loadMore = async () => {
break;
}
}
};
}
/** 浏览历史初始化 */
const conversation = ref<MallKefuConversationApi.Conversation>(
{} as MallKefuConversationApi.Conversation,
); // 用户会话
const initHistory = async (val: MallKefuConversationApi.Conversation) => {
async function initHistory(val: MallKefuConversationApi.Conversation) {
activeTab.value = '会员信息';
conversation.value = val;
await nextTick();
await getHistoryList();
};
}
defineExpose({ initHistory });
/** 处理消息列表滚动事件(debounce 限流) */
@@ -106,7 +106,8 @@ const WALLET_INIT_DATA = {
totalRecharge: 0,
} as WalletApi.WalletVO; // 钱包初始化数据
const wallet = ref<WalletApi.WalletVO>(WALLET_INIT_DATA); // 钱包信息
const getUserWallet = async () => {
async function getUserWallet() {
if (!conversation.value.userId) {
wallet.value = WALLET_INIT_DATA;
return;
@@ -114,12 +115,12 @@ const getUserWallet = async () => {
wallet.value =
(await WalletApi.getWallet({ userId: conversation.value.userId })) ||
WALLET_INIT_DATA;
};
}
/** 获得用户 */
const loading = ref(true); // 加载中
const user = ref<UserApi.UserVO>({} as UserApi.UserVO);
const getUserData = async () => {
async function getUserData() {
loading.value = true;
try {
const res = await UserApi.getUser(conversation.value.userId);
@@ -132,7 +133,7 @@ const getUserData = async () => {
} finally {
loading.value = false;
}
};
}
</script>
<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;
const res = await getOrderPage(queryParams);
total.value = res.total;
list.value = res.list;
};
}
/** 加载下一页数据 */
const loadMore = async () => {
async function loadMore() {
if (skipGetMessageList.value) {
return;
}
@@ -41,7 +41,7 @@ const loadMore = async () => {
total.value = res.total;
// 使用展开运算符替代 concat 方法
list.value = [...list.value, ...res.list];
};
}
defineExpose({ getHistoryList, loadMore });
</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;
const res = await getBrowseHistoryPage(queryParams);
total.value = res.total;
list.value = res.list;
};
}
/** 加载下一页数据 */
const loadMore = async () => {
async function loadMore() {
if (skipGetMessageList.value) {
return;
}
@@ -43,7 +43,7 @@ const loadMore = async () => {
total.value = res.total;
// 使用展开运算符替代 concat 方法
list.value = [...list.value, ...res.list];
};
}
defineExpose({ getHistoryList, loadMore });
</script>

View File

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

View File

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

View File

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