refactor: kefu
This commit is contained in:
@@ -7,7 +7,7 @@ import { Page } from '@vben/common-ui';
|
|||||||
import { useAccessStore } from '@vben/stores';
|
import { useAccessStore } from '@vben/stores';
|
||||||
|
|
||||||
import { useWebSocket } from '@vueuse/core';
|
import { useWebSocket } from '@vueuse/core';
|
||||||
import { Layout, message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
import { useMallKefuStore } from '#/store/mall/kefu';
|
import { useMallKefuStore } from '#/store/mall/kefu';
|
||||||
|
|
||||||
@@ -104,13 +104,19 @@ onBeforeUnmount(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page auto-content-height>
|
<Page auto-content-height>
|
||||||
<Layout.Content class="absolute left-0 top-0 m-4 flex h-full w-full flex-1">
|
<div class="flex h-full antialiased">
|
||||||
<!-- 会话列表 -->
|
<div class="flex h-full w-full flex-row overflow-x-hidden">
|
||||||
<ConversationList ref="conversationListRef" @change="handleChange" />
|
<!-- 会话列表 -->
|
||||||
<!-- 会话详情(选中会话的消息列表) -->
|
<ConversationList
|
||||||
<MessageList ref="messageListRef" />
|
class="w-1/6"
|
||||||
<!-- 会员信息(选中会话的会员信息) -->
|
ref="conversationListRef"
|
||||||
<MemberInfo ref="memberInfoRef" />
|
@change="handleChange"
|
||||||
</Layout.Content>
|
/>
|
||||||
|
<!-- 会话详情(选中会话的消息列表) -->
|
||||||
|
<MessageList class="w-4/6" ref="messageListRef" />
|
||||||
|
<!-- 会员信息(选中会话的会员信息) -->
|
||||||
|
<MemberInfo class="w-1/6" ref="memberInfoRef" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { confirm } from '@vben/common-ui';
|
|||||||
import { IconifyIcon } from '@vben/icons';
|
import { IconifyIcon } from '@vben/icons';
|
||||||
import { formatPast, jsonParse } from '@vben/utils';
|
import { formatPast, jsonParse } from '@vben/utils';
|
||||||
|
|
||||||
import { Avatar, Badge, Layout, message } from 'ant-design-vue';
|
import { Avatar, message } from 'ant-design-vue';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
deleteConversation,
|
deleteConversation,
|
||||||
@@ -158,38 +158,36 @@ onBeforeUnmount(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Layout.Sider
|
<div
|
||||||
class="bg-card relative flex h-full flex-col justify-between overflow-hidden p-4"
|
class="bg-background flex flex-shrink-0 flex-col border-r border-gray-200 p-4"
|
||||||
width="260px"
|
|
||||||
>
|
>
|
||||||
<div class="m-4 border-b border-gray-200 pb-2 font-bold">
|
<div class="flex h-12 w-full flex-row items-center justify-between">
|
||||||
会话记录({{ kefuStore.getConversationList.length }})
|
<span class="text-lg font-bold">会话记录</span>
|
||||||
|
<span
|
||||||
|
class="flex h-4 w-4 items-center justify-center rounded-full bg-indigo-100 text-indigo-600"
|
||||||
|
>
|
||||||
|
{{ kefuStore.getConversationList.length }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div class="mt-2 flex flex-col">
|
||||||
v-for="item in kefuStore.getConversationList"
|
<div class="-mx-2 mt-4 flex h-48 flex-col space-y-1 overflow-y-auto">
|
||||||
:key="item.id"
|
<button
|
||||||
:class="{
|
v-for="(item, index) in kefuStore.getConversationList"
|
||||||
'bg-gray-500/50': item.id === activeConversationId,
|
:key="index"
|
||||||
}"
|
class="flex flex-row items-center rounded-xl p-2 hover:bg-gray-100"
|
||||||
class="flex h-14 cursor-pointer items-center px-3"
|
:class="{
|
||||||
@click="openRightMessage(item)"
|
'bg-gray-500/50': item.id === activeConversationId,
|
||||||
@contextmenu.prevent="rightClick($event as PointerEvent, item)"
|
}"
|
||||||
>
|
@click="openRightMessage(item)"
|
||||||
<div class="flex w-full items-center justify-center">
|
@contextmenu.prevent="rightClick($event as PointerEvent, item)"
|
||||||
<div class="flex h-12 w-12 items-center justify-center">
|
>
|
||||||
<!-- 头像 + 未读 -->
|
<div
|
||||||
<Badge :max="99" :value="item.adminUnreadMessageCount">
|
class="flex h-8 w-8 items-center justify-center rounded-full bg-gray-200"
|
||||||
|
>
|
||||||
<Avatar :src="item.userAvatar" alt="avatar" />
|
<Avatar :src="item.userAvatar" alt="avatar" />
|
||||||
</Badge>
|
</div>
|
||||||
</div>
|
<div class="ml-2 text-sm font-semibold">
|
||||||
<div class="ml-3 w-full">
|
{{ item.userNickname || 'null' }}
|
||||||
<div class="flex w-full items-center justify-between">
|
|
||||||
<span class="line-clamp-1 min-w-0 max-w-[60%]">
|
|
||||||
{{ item.userNickname || 'null' }}
|
|
||||||
</span>
|
|
||||||
<span class="text-sm text-gray-500">
|
|
||||||
{{ lastMessageTimeMap.get(item.id) ?? '计算中' }}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<!-- 最后聊天内容 -->
|
<!-- 最后聊天内容 -->
|
||||||
<div
|
<div
|
||||||
@@ -201,7 +199,13 @@ onBeforeUnmount(() => {
|
|||||||
"
|
"
|
||||||
class="line-clamp-1 flex items-center text-sm text-gray-500"
|
class="line-clamp-1 flex items-center text-sm text-gray-500"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
<div
|
||||||
|
v-if="item.adminUnreadMessageCount > 0"
|
||||||
|
class="ml-auto flex h-4 w-4 items-center justify-center rounded bg-red-500 text-xs leading-none text-white"
|
||||||
|
>
|
||||||
|
{{ item.adminUnreadMessageCount }}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -242,5 +246,5 @@ onBeforeUnmount(() => {
|
|||||||
取消
|
取消
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</Layout.Sider>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,18 +1,15 @@
|
|||||||
<!-- 右侧信息:会员信息 + 最近浏览 + 交易订单 -->
|
<!-- 右侧信息:会员信息 + 最近浏览 + 交易订单 -->
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { UseScrollReturn } from '@vueuse/core';
|
|
||||||
|
|
||||||
import type { MallKefuConversationApi } from '#/api/mall/promotion/kefu/conversation';
|
import type { MallKefuConversationApi } from '#/api/mall/promotion/kefu/conversation';
|
||||||
import type { MemberUserApi } from '#/api/member/user';
|
import type { MemberUserApi } from '#/api/member/user';
|
||||||
import type { PayWalletApi } from '#/api/pay/wallet/balance';
|
import type { PayWalletApi } from '#/api/pay/wallet/balance';
|
||||||
|
|
||||||
import { computed, nextTick, ref } from 'vue';
|
import { computed, nextTick, ref, toRefs, watch } from 'vue';
|
||||||
|
|
||||||
import { isEmpty } from '@vben/utils';
|
import { isEmpty } from '@vben/utils';
|
||||||
|
|
||||||
import { vScroll } from '@vueuse/components';
|
import { useScroll } from '@vueuse/core';
|
||||||
import { useDebounceFn } from '@vueuse/core';
|
import { Empty, message } from 'ant-design-vue';
|
||||||
import { Empty, Layout, message } from 'ant-design-vue';
|
|
||||||
|
|
||||||
import { getUser } from '#/api/member/user';
|
import { getUser } from '#/api/member/user';
|
||||||
import { getWallet } from '#/api/pay/wallet/balance';
|
import { getWallet } from '#/api/pay/wallet/balance';
|
||||||
@@ -93,12 +90,16 @@ defineExpose({ initHistory });
|
|||||||
|
|
||||||
/** 处理消息列表滚动事件(debounce 限流) */
|
/** 处理消息列表滚动事件(debounce 限流) */
|
||||||
const scrollbarRef = ref<InstanceType<any>>();
|
const scrollbarRef = ref<InstanceType<any>>();
|
||||||
const handleScroll = useDebounceFn(async (state: UseScrollReturn) => {
|
const { arrivedState } = useScroll(scrollbarRef);
|
||||||
const { arrivedState } = state;
|
const { bottom } = toRefs(arrivedState);
|
||||||
if (arrivedState.bottom) {
|
watch(
|
||||||
await loadMore();
|
() => bottom.value,
|
||||||
}
|
async (newVal) => {
|
||||||
}, 200);
|
if (newVal) {
|
||||||
|
await loadMore();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
/** 查询用户钱包信息 */
|
/** 查询用户钱包信息 */
|
||||||
// TODO @jawe:idea 的导入报错;需要看下;
|
// TODO @jawe:idea 的导入报错;需要看下;
|
||||||
@@ -139,18 +140,16 @@ async function getUserData() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Layout
|
<div class="bg-background relative">
|
||||||
class="bg-card relative w-72 after:absolute after:left-0 after:top-0 after:h-full after:w-[1px] after:scale-x-[0.3] after:bg-gray-200 after:content-['']"
|
<div
|
||||||
>
|
class="relative flex h-12 items-center justify-around before:absolute before:bottom-0 before:left-0 before:h-[1px] before:w-full before:scale-y-[0.3] before:bg-gray-200 before:content-['']"
|
||||||
<Layout.Header
|
|
||||||
class="!bg-card relative flex items-center justify-around before:absolute before:bottom-0 before:left-0 before:h-[1px] before:w-full before:scale-y-[0.3] before:bg-gray-200 before:content-['']"
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
:class="{
|
:class="{
|
||||||
'before:border-b-2 before:border-gray-500/50':
|
'before:border-b-2 before:border-gray-500/50':
|
||||||
tabActivation('会员信息'),
|
tabActivation('会员信息'),
|
||||||
}"
|
}"
|
||||||
class="relative flex h-full w-full cursor-pointer items-center justify-center before:pointer-events-none before:absolute before:inset-0 before:content-[''] hover:before:border-b-2 hover:before:border-gray-500/50"
|
class="relative flex w-full cursor-pointer items-center justify-center before:pointer-events-none before:absolute before:inset-0 before:content-[''] hover:before:border-b-2 hover:before:border-gray-500/50"
|
||||||
@click="handleClick('会员信息')"
|
@click="handleClick('会员信息')"
|
||||||
>
|
>
|
||||||
会员信息
|
会员信息
|
||||||
@@ -160,7 +159,7 @@ async function getUserData() {
|
|||||||
'before:border-b-2 before:border-gray-500/50':
|
'before:border-b-2 before:border-gray-500/50':
|
||||||
tabActivation('最近浏览'),
|
tabActivation('最近浏览'),
|
||||||
}"
|
}"
|
||||||
class="relative flex h-full w-full cursor-pointer items-center justify-center before:pointer-events-none before:absolute before:inset-0 before:content-[''] hover:before:border-b-2 hover:before:border-gray-500/50"
|
class="relative flex w-full cursor-pointer items-center justify-center before:pointer-events-none before:absolute before:inset-0 before:content-[''] hover:before:border-b-2 hover:before:border-gray-500/50"
|
||||||
@click="handleClick('最近浏览')"
|
@click="handleClick('最近浏览')"
|
||||||
>
|
>
|
||||||
最近浏览
|
最近浏览
|
||||||
@@ -170,18 +169,18 @@ async function getUserData() {
|
|||||||
'before:border-b-2 before:border-gray-500/50':
|
'before:border-b-2 before:border-gray-500/50':
|
||||||
tabActivation('交易订单'),
|
tabActivation('交易订单'),
|
||||||
}"
|
}"
|
||||||
class="relative flex h-full w-full cursor-pointer items-center justify-center before:pointer-events-none before:absolute before:inset-0 before:content-[''] hover:before:border-b-2 hover:before:border-gray-500/50"
|
class="relative flex w-full cursor-pointer items-center justify-center before:pointer-events-none before:absolute before:inset-0 before:content-[''] hover:before:border-b-2 hover:before:border-gray-500/50"
|
||||||
@click="handleClick('交易订单')"
|
@click="handleClick('交易订单')"
|
||||||
>
|
>
|
||||||
交易订单
|
交易订单
|
||||||
</div>
|
</div>
|
||||||
</Layout.Header>
|
</div>
|
||||||
<Layout.Content class="relative m-0 h-full w-full p-2">
|
<div class="relative m-0 w-full p-2">
|
||||||
<template v-if="!isEmpty(conversation)">
|
<template v-if="!isEmpty(conversation)">
|
||||||
<div
|
<div
|
||||||
v-loading="loading"
|
v-loading="loading"
|
||||||
v-if="activeTab === '会员信息'"
|
v-if="activeTab === '会员信息'"
|
||||||
class="relative h-full overflow-y-auto overflow-x-hidden"
|
class="relative overflow-y-auto overflow-x-hidden"
|
||||||
>
|
>
|
||||||
<!-- 基本信息 -->
|
<!-- 基本信息 -->
|
||||||
<BasicInfo :user="user" mode="kefu">
|
<BasicInfo :user="user" mode="kefu">
|
||||||
@@ -205,7 +204,6 @@ async function getUserData() {
|
|||||||
<div
|
<div
|
||||||
v-show="activeTab !== '会员信息'"
|
v-show="activeTab !== '会员信息'"
|
||||||
ref="scrollbarRef"
|
ref="scrollbarRef"
|
||||||
v-scroll="handleScroll"
|
|
||||||
class="relative h-full overflow-y-auto overflow-x-hidden"
|
class="relative h-full overflow-y-auto overflow-x-hidden"
|
||||||
>
|
>
|
||||||
<!-- 最近浏览 -->
|
<!-- 最近浏览 -->
|
||||||
@@ -221,6 +219,6 @@ async function getUserData() {
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<Empty v-else description="请选择左侧的一个会话后开始" class="mt-[20%]" />
|
<Empty v-else description="请选择左侧的一个会话后开始" class="mt-[20%]" />
|
||||||
</Layout.Content>
|
</div>
|
||||||
</Layout>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,27 +1,17 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { UseScrollReturn } from '@vueuse/core';
|
|
||||||
|
|
||||||
import type { Emoji } from './tools/emoji';
|
import type { Emoji } from './tools/emoji';
|
||||||
|
|
||||||
import type { MallKefuConversationApi } from '#/api/mall/promotion/kefu/conversation';
|
import type { MallKefuConversationApi } from '#/api/mall/promotion/kefu/conversation';
|
||||||
import type { MallKefuMessageApi } from '#/api/mall/promotion/kefu/message';
|
import type { MallKefuMessageApi } from '#/api/mall/promotion/kefu/message';
|
||||||
|
|
||||||
import { computed, reactive, ref, unref } from 'vue';
|
import { computed, reactive, ref, toRefs, unref, watch } from 'vue';
|
||||||
|
|
||||||
import { UserTypeEnum } from '@vben/constants';
|
import { UserTypeEnum } from '@vben/constants';
|
||||||
import { IconifyIcon } from '@vben/icons';
|
import { IconifyIcon } from '@vben/icons';
|
||||||
import { formatDate, isEmpty, jsonParse } from '@vben/utils';
|
import { formatDate, isEmpty, jsonParse } from '@vben/utils';
|
||||||
|
|
||||||
import { vScroll } from '@vueuse/components';
|
import { useScroll } from '@vueuse/core';
|
||||||
import { useDebounceFn, useScroll } from '@vueuse/core';
|
import { Avatar, Empty, Image, notification, Textarea } from 'ant-design-vue';
|
||||||
import {
|
|
||||||
Avatar,
|
|
||||||
Empty,
|
|
||||||
Image,
|
|
||||||
Layout,
|
|
||||||
notification,
|
|
||||||
Textarea,
|
|
||||||
} from 'ant-design-vue';
|
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||||
|
|
||||||
@@ -206,7 +196,9 @@ async function sendMessage(msg: MallKefuMessageApi.MessageSend) {
|
|||||||
|
|
||||||
/** 滚动到底部 */
|
/** 滚动到底部 */
|
||||||
const scrollbarRef = ref<HTMLElement | null>(null);
|
const scrollbarRef = ref<HTMLElement | null>(null);
|
||||||
const { y } = useScroll(scrollbarRef);
|
const { y, arrivedState } = useScroll(scrollbarRef);
|
||||||
|
|
||||||
|
const { bottom } = toRefs(arrivedState);
|
||||||
|
|
||||||
async function scrollToBottom() {
|
async function scrollToBottom() {
|
||||||
if (!scrollbarRef.value) return;
|
if (!scrollbarRef.value) return;
|
||||||
@@ -228,23 +220,26 @@ async function handleToNewMessage() {
|
|||||||
await scrollToBottom();
|
await scrollToBottom();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 处理消息列表滚动事件(debounce 限流) */
|
watch(
|
||||||
const handleScroll = useDebounceFn((state: UseScrollReturn) => {
|
() => bottom.value,
|
||||||
const { arrivedState } = state;
|
(newVal) => {
|
||||||
if (skipGetMessageList.value) {
|
if (newVal) {
|
||||||
return;
|
if (skipGetMessageList.value) {
|
||||||
}
|
return;
|
||||||
// 滚动到底部了
|
}
|
||||||
if (arrivedState.bottom) {
|
// 滚动到底部了
|
||||||
loadHistory.value = false;
|
if (newVal) {
|
||||||
refreshMessageList();
|
loadHistory.value = false;
|
||||||
}
|
refreshMessageList();
|
||||||
|
}
|
||||||
|
|
||||||
// 触顶自动加载下一页数据
|
// 触顶自动加载下一页数据
|
||||||
if (arrivedState.top) {
|
if (arrivedState.top) {
|
||||||
handleOldMessage();
|
handleOldMessage();
|
||||||
}
|
}
|
||||||
}, 200);
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
/** 加载历史消息 */
|
/** 加载历史消息 */
|
||||||
async function handleOldMessage() {
|
async function handleOldMessage() {
|
||||||
loadHistory.value = true;
|
loadHistory.value = true;
|
||||||
@@ -264,164 +259,163 @@ function showTime(item: MallKefuMessageApi.Message, index: number) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Layout
|
<div
|
||||||
v-if="showMessageList()"
|
v-if="showMessageList()"
|
||||||
class="bg-card relative w-[calc(100%-300px-260px)]"
|
class="bg-background flex h-full flex-auto flex-col p-4"
|
||||||
>
|
>
|
||||||
<Layout.Header
|
<div class="flex h-full flex-auto flex-shrink-0 flex-col">
|
||||||
class="!bg-card border-border flex items-center justify-between border-b"
|
<div class="flex h-12 w-full flex-row items-center justify-between">
|
||||||
>
|
<span class="text-lg font-bold">{{ conversation.userNickname }}</span>
|
||||||
<div class="text-lg font-bold">{{ conversation.userNickname }}</div>
|
</div>
|
||||||
</Layout.Header>
|
|
||||||
<Layout.Content class="relative m-0 h-full w-full p-0">
|
|
||||||
<div
|
<div
|
||||||
ref="scrollbarRef"
|
ref="scrollbarRef"
|
||||||
class="absolute inset-0 m-0 overflow-y-auto overflow-x-hidden p-0"
|
class="mb-4 flex flex-col overflow-x-auto rounded-lg bg-gray-100 p-2"
|
||||||
v-scroll="handleScroll"
|
|
||||||
>
|
>
|
||||||
<!-- <div v-if="refreshContent" ref="innerRef" class="w-full px-[10px] absolute inset-0 m-0 overflow-x-hidden p-0 overflow-y-auto"> -->
|
<div class="flex flex-col">
|
||||||
<!-- 消息列表 -->
|
<!-- 消息列表 -->
|
||||||
<div
|
|
||||||
v-for="(item, index) in getMessageList0"
|
|
||||||
:key="item.id"
|
|
||||||
class="w-full"
|
|
||||||
>
|
|
||||||
<div class="mb-5 flex items-center justify-center">
|
|
||||||
<!-- 日期 -->
|
|
||||||
<div
|
|
||||||
v-if="
|
|
||||||
item.contentType !== KeFuMessageContentTypeEnum.SYSTEM &&
|
|
||||||
showTime(item, index)
|
|
||||||
"
|
|
||||||
class="w-fit rounded-lg bg-black/10 px-2 text-xs text-white"
|
|
||||||
>
|
|
||||||
{{ formatDate(item.createTime) }}
|
|
||||||
</div>
|
|
||||||
<!-- 系统消息 -->
|
|
||||||
<div
|
|
||||||
v-if="item.contentType === KeFuMessageContentTypeEnum.SYSTEM"
|
|
||||||
class="w-fit rounded-lg bg-black/10 px-2 text-xs text-white"
|
|
||||||
>
|
|
||||||
{{ item.content }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
:class="[
|
v-for="(item, index) in getMessageList0"
|
||||||
item.senderType === UserTypeEnum.MEMBER
|
:key="item.id"
|
||||||
? 'justify-start'
|
class="w-full"
|
||||||
: item.senderType === UserTypeEnum.ADMIN
|
|
||||||
? 'justify-end'
|
|
||||||
: '',
|
|
||||||
]"
|
|
||||||
class="mb-5 flex w-full"
|
|
||||||
>
|
>
|
||||||
<Avatar
|
<div class="mb-5 flex items-center justify-center">
|
||||||
v-if="item.senderType === UserTypeEnum.MEMBER"
|
<!-- 日期 -->
|
||||||
:src="conversation.userAvatar"
|
<div
|
||||||
alt="avatar"
|
v-if="
|
||||||
class="size-8"
|
item.contentType !== KeFuMessageContentTypeEnum.SYSTEM &&
|
||||||
/>
|
showTime(item, index)
|
||||||
<div
|
"
|
||||||
:class="{
|
class="w-fit rounded-lg bg-black/10 px-2 text-xs"
|
||||||
'w-auto max-w-[50%] p-1 font-medium text-gray-500 transition-all duration-200 hover:scale-105':
|
>
|
||||||
KeFuMessageContentTypeEnum.TEXT === item.contentType,
|
{{ formatDate(item.createTime) }}
|
||||||
'm-1 break-words rounded-lg bg-gray-100':
|
</div>
|
||||||
KeFuMessageContentTypeEnum.TEXT === item.contentType &&
|
<!-- 系统消息 -->
|
||||||
item.senderType === UserTypeEnum.MEMBER,
|
<div
|
||||||
'm-1 break-words rounded-lg bg-blue-50':
|
v-if="item.contentType === KeFuMessageContentTypeEnum.SYSTEM"
|
||||||
KeFuMessageContentTypeEnum.TEXT === item.contentType &&
|
class="w-fit rounded-lg bg-black/10 px-2 text-xs"
|
||||||
item.senderType === UserTypeEnum.ADMIN,
|
>
|
||||||
}"
|
{{ item.content }}
|
||||||
>
|
</div>
|
||||||
<!-- 文本消息 -->
|
</div>
|
||||||
<MessageItem :message="item">
|
<div
|
||||||
<template
|
:class="[
|
||||||
v-if="KeFuMessageContentTypeEnum.TEXT === item.contentType"
|
item.senderType === UserTypeEnum.MEMBER
|
||||||
>
|
? 'justify-start'
|
||||||
<div
|
: item.senderType === UserTypeEnum.ADMIN
|
||||||
v-dompurify-html="
|
? 'justify-end'
|
||||||
replaceEmoji(getMessageContent(item).text || item.content)
|
: '',
|
||||||
"
|
]"
|
||||||
class="h-full w-full text-justify"
|
class="mb-5 flex w-full"
|
||||||
></div>
|
>
|
||||||
</template>
|
<Avatar
|
||||||
</MessageItem>
|
v-if="item.senderType === UserTypeEnum.MEMBER"
|
||||||
<!-- 图片消息 -->
|
:src="conversation.userAvatar"
|
||||||
<MessageItem :message="item">
|
alt="avatar"
|
||||||
<Image
|
class="size-8"
|
||||||
v-if="KeFuMessageContentTypeEnum.IMAGE === item.contentType"
|
/>
|
||||||
:initial-index="0"
|
<div
|
||||||
:preview-src-list="[
|
:class="{
|
||||||
getMessageContent(item).picUrl || item.content,
|
'w-auto max-w-[50%] p-1 font-medium text-gray-500 transition-all duration-200 hover:scale-105':
|
||||||
]"
|
KeFuMessageContentTypeEnum.TEXT === item.contentType,
|
||||||
:src="getMessageContent(item).picUrl || item.content"
|
'm-1 break-words rounded-lg bg-gray-100':
|
||||||
class="mx-2 !w-52"
|
KeFuMessageContentTypeEnum.TEXT === item.contentType &&
|
||||||
fit="contain"
|
item.senderType === UserTypeEnum.MEMBER,
|
||||||
preview-teleported
|
'm-1 break-words rounded-lg bg-blue-50':
|
||||||
/>
|
KeFuMessageContentTypeEnum.TEXT === item.contentType &&
|
||||||
</MessageItem>
|
item.senderType === UserTypeEnum.ADMIN,
|
||||||
<!-- 商品消息 -->
|
}"
|
||||||
<MessageItem :message="item">
|
>
|
||||||
<ProductItem
|
<!-- 文本消息 -->
|
||||||
v-if="KeFuMessageContentTypeEnum.PRODUCT === item.contentType"
|
<MessageItem :message="item">
|
||||||
:pic-url="getMessageContent(item).picUrl"
|
<template
|
||||||
:price="getMessageContent(item).price"
|
v-if="KeFuMessageContentTypeEnum.TEXT === item.contentType"
|
||||||
:sales-count="getMessageContent(item).salesCount"
|
>
|
||||||
:spu-id="getMessageContent(item).spuId"
|
<div
|
||||||
:stock="getMessageContent(item).stock"
|
v-dompurify-html="
|
||||||
:title="getMessageContent(item).spuName"
|
replaceEmoji(
|
||||||
class="mx-2 max-w-80"
|
getMessageContent(item).text || item.content,
|
||||||
/>
|
)
|
||||||
</MessageItem>
|
"
|
||||||
<!-- 订单消息 -->
|
class="h-full w-full text-justify"
|
||||||
<MessageItem :message="item">
|
></div>
|
||||||
<OrderItem
|
</template>
|
||||||
v-if="KeFuMessageContentTypeEnum.ORDER === item.contentType"
|
</MessageItem>
|
||||||
:message="item"
|
<!-- 图片消息 -->
|
||||||
class="mx-2 max-w-full"
|
<MessageItem :message="item">
|
||||||
/>
|
<Image
|
||||||
</MessageItem>
|
v-if="KeFuMessageContentTypeEnum.IMAGE === item.contentType"
|
||||||
|
:initial-index="0"
|
||||||
|
:preview-src-list="[
|
||||||
|
getMessageContent(item).picUrl || item.content,
|
||||||
|
]"
|
||||||
|
:src="getMessageContent(item).picUrl || item.content"
|
||||||
|
class="mx-2 !w-52"
|
||||||
|
fit="contain"
|
||||||
|
preview-teleported
|
||||||
|
/>
|
||||||
|
</MessageItem>
|
||||||
|
<!-- 商品消息 -->
|
||||||
|
<MessageItem :message="item">
|
||||||
|
<ProductItem
|
||||||
|
v-if="
|
||||||
|
KeFuMessageContentTypeEnum.PRODUCT === item.contentType
|
||||||
|
"
|
||||||
|
:pic-url="getMessageContent(item).picUrl"
|
||||||
|
:price="getMessageContent(item).price"
|
||||||
|
:sales-count="getMessageContent(item).salesCount"
|
||||||
|
:spu-id="getMessageContent(item).spuId"
|
||||||
|
:stock="getMessageContent(item).stock"
|
||||||
|
:title="getMessageContent(item).spuName"
|
||||||
|
class="mx-2 max-w-80"
|
||||||
|
/>
|
||||||
|
</MessageItem>
|
||||||
|
<!-- 订单消息 -->
|
||||||
|
<MessageItem :message="item">
|
||||||
|
<OrderItem
|
||||||
|
v-if="KeFuMessageContentTypeEnum.ORDER === item.contentType"
|
||||||
|
:message="item"
|
||||||
|
class="mx-2 max-w-full"
|
||||||
|
/>
|
||||||
|
</MessageItem>
|
||||||
|
</div>
|
||||||
|
<Avatar
|
||||||
|
v-if="item.senderType === UserTypeEnum.ADMIN"
|
||||||
|
:src="item.senderAvatar"
|
||||||
|
alt="avatar"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Avatar
|
|
||||||
v-if="item.senderType === UserTypeEnum.ADMIN"
|
|
||||||
:src="item.senderAvatar"
|
|
||||||
alt="avatar"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- </div> -->
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-show="showNewMessageTip"
|
v-show="showNewMessageTip"
|
||||||
class="bg-card absolute bottom-9 right-9 z-10 flex cursor-pointer items-center rounded-full p-2.5 text-xs shadow-md"
|
class="absolute bottom-9 right-9 z-10 flex cursor-pointer items-center rounded-lg p-2 text-xs shadow-md"
|
||||||
@click="handleToNewMessage"
|
@click="handleToNewMessage"
|
||||||
>
|
>
|
||||||
<span>有新消息</span>
|
<span>有新消息</span>
|
||||||
<IconifyIcon class="ml-1" icon="lucide:arrow-down-from-line" />
|
<IconifyIcon class="ml-1" icon="lucide:arrow-down-from-line" />
|
||||||
</div>
|
</div>
|
||||||
</Layout.Content>
|
<div class="flex flex-col">
|
||||||
<Layout.Footer class="!bg-card m-0 flex flex-col p-0">
|
<div class="border-border -mt-3 flex flex-col rounded-xl border">
|
||||||
<div class="border-border flex flex-col rounded-xl border p-2">
|
<div class="flex h-10 w-full items-center">
|
||||||
<div class="flex h-11 w-full items-center">
|
<EmojiSelectPopover @select-emoji="handleEmojiSelect" />
|
||||||
<EmojiSelectPopover @select-emoji="handleEmojiSelect" />
|
<PictureSelectUpload
|
||||||
<PictureSelectUpload
|
class="ml-4 mt-1 cursor-pointer"
|
||||||
class="ml-4 mt-1 cursor-pointer"
|
@send-picture="handleSendPicture"
|
||||||
@send-picture="handleSendPicture"
|
/>
|
||||||
|
</div>
|
||||||
|
<Textarea
|
||||||
|
v-model:value="message"
|
||||||
|
:rows="4"
|
||||||
|
class="border-none p-2"
|
||||||
|
placeholder="输入消息,Enter发送,Shift+Enter换行"
|
||||||
|
@press-enter="handleSendMessage"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Textarea
|
|
||||||
v-model:value="message"
|
|
||||||
:rows="6"
|
|
||||||
class="border-none"
|
|
||||||
placeholder="输入消息,Enter发送,Shift+Enter换行"
|
|
||||||
@press-enter="handleSendMessage"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</Layout.Footer>
|
</div>
|
||||||
</Layout>
|
</div>
|
||||||
<Layout v-else class="bg-card relative w-[calc(100vw-300px-260px)]">
|
<div v-else class="bg-background relative">
|
||||||
<Layout.Content>
|
<Empty description="请选择左侧的一个会话后开始" class="mt-[20%]" />
|
||||||
<Empty description="请选择左侧的一个会话后开始" class="mt-[20%]" />
|
</div>
|
||||||
</Layout.Content>
|
|
||||||
</Layout>
|
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user