Compare commits
5 Commits
00d292e5cf
...
f0a74916c7
Author | SHA1 | Date | |
---|---|---|---|
f0a74916c7 | |||
7cb178dcc9 | |||
262b098be8 | |||
ef9ebbfaba | |||
9443f4959a |
@ -6,6 +6,7 @@ clone:
|
|||||||
disable: true
|
disable: true
|
||||||
|
|
||||||
steps: # 定义流水线执行步骤,这些步骤将顺序执行
|
steps: # 定义流水线执行步骤,这些步骤将顺序执行
|
||||||
|
- name: build-java
|
||||||
- image: appleboy/drone-ssh # SSH工具镜像
|
- image: appleboy/drone-ssh # SSH工具镜像
|
||||||
settings:
|
settings:
|
||||||
host: 1.14.205.126 # 远程连接地址
|
host: 1.14.205.126 # 远程连接地址
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="kefu">
|
<div class="kefu">
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-for="item in conversationList"
|
v-for="item in conversationList"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
@ -9,7 +10,7 @@
|
|||||||
@contextmenu.prevent="rightClick($event as PointerEvent, item)"
|
@contextmenu.prevent="rightClick($event as PointerEvent, item)"
|
||||||
>
|
>
|
||||||
<div class="flex justify-center items-center w-100%">
|
<div class="flex justify-center items-center w-100%">
|
||||||
<div class="flex justify-center items-center w-50px h-50px">
|
<div class="flex justify-center items-center w-40px h-40px">
|
||||||
<!-- 头像 + 未读 -->
|
<!-- 头像 + 未读 -->
|
||||||
<el-badge
|
<el-badge
|
||||||
:hidden="item.adminUnreadMessageCount === 0"
|
:hidden="item.adminUnreadMessageCount === 0"
|
||||||
@ -19,10 +20,10 @@
|
|||||||
<el-avatar :src="item.userAvatar" alt="avatar" />
|
<el-avatar :src="item.userAvatar" alt="avatar" />
|
||||||
</el-badge>
|
</el-badge>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-10px w-100%">
|
<div class="ml-3px w-full">
|
||||||
<div class="flex justify-between items-center w-100%">
|
<div class="flex justify-between items-center w-full">
|
||||||
<span class="username">{{ item.userNickname }}</span>
|
<span class="username">{{ item.userNickname }}</span>
|
||||||
<span class="color-[var(--left-menu-text-color)]" style="font-size: 13px">
|
<span class="color-[var(--left-menu-text-color)]" style="font-size: 12px">
|
||||||
{{ formatPast(item.lastMessageTime, 'YYYY-MM-DD') }}
|
{{ formatPast(item.lastMessageTime, 'YYYY-MM-DD') }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -182,12 +183,13 @@ watch(showRightMenu, (val) => {
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.kefu {
|
.kefu {
|
||||||
&-conversation {
|
&-conversation {
|
||||||
height: 60px;
|
height: 55px;
|
||||||
padding: 10px;
|
padding: 8px;
|
||||||
//background-color: #fff;
|
//background-color: #fff;
|
||||||
transition: border-left 0.05s ease-in-out; /* 设置过渡效果 */
|
transition: border-left 0.05s ease-in-out; /* 设置过渡效果 */
|
||||||
|
|
||||||
.username {
|
.username {
|
||||||
|
font-size: 20%;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
max-width: 60%;
|
max-width: 60%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -198,7 +200,7 @@ watch(showRightMenu, (val) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.last-message {
|
.last-message {
|
||||||
font-size: 13px;
|
font-size: 15px;
|
||||||
width: 200px;
|
width: 200px;
|
||||||
overflow: hidden; // 隐藏超出的文本
|
overflow: hidden; // 隐藏超出的文本
|
||||||
white-space: nowrap; // 禁止换行
|
white-space: nowrap; // 禁止换行
|
||||||
|
@ -10,8 +10,7 @@
|
|||||||
<div v-for="(item, index) in getMessageList0" :key="item.id" class="w-[100%]">
|
<div v-for="(item, index) in getMessageList0" :key="item.id" class="w-[100%]">
|
||||||
<div class="flex justify-center items-center mb-20px">
|
<div class="flex justify-center items-center mb-20px">
|
||||||
<!-- 日期 -->
|
<!-- 日期 -->
|
||||||
<div
|
<div v-if="
|
||||||
v-if="
|
|
||||||
item.contentType !== KeFuMessageContentTypeEnum.SYSTEM && showTime(item, index)
|
item.contentType !== KeFuMessageContentTypeEnum.SYSTEM && showTime(item, index)
|
||||||
" class="date-message">
|
" class="date-message">
|
||||||
{{ formatDate(item.createTime) }}
|
{{ formatDate(item.createTime) }}
|
||||||
@ -21,19 +20,16 @@ v-if="
|
|||||||
{{ item.content }}
|
{{ item.content }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div :class="[
|
||||||
:class="[
|
|
||||||
item.senderType === UserTypeEnum.MEMBER
|
item.senderType === UserTypeEnum.MEMBER
|
||||||
? `ss-row-left`
|
? `ss-row-left`
|
||||||
: item.senderType === UserTypeEnum.ADMIN
|
: item.senderType === UserTypeEnum.ADMIN
|
||||||
? `ss-row-right`
|
? `ss-row-right`
|
||||||
: ''
|
: ''
|
||||||
]" class="flex mb-20px w-[100%]">
|
]" class="flex mb-20px w-[100%]">
|
||||||
<el-avatar
|
<el-avatar v-if="item.senderType === UserTypeEnum.MEMBER" :src="conversation.userAvatar"
|
||||||
v-if="item.senderType === UserTypeEnum.MEMBER" :src="conversation.userAvatar"
|
|
||||||
alt="avatar" class="w-60px h-60px" />
|
alt="avatar" class="w-60px h-60px" />
|
||||||
<div
|
<div :class="{ 'kefu-message': KeFuMessageContentTypeEnum.TEXT === item.contentType }"
|
||||||
:class="{ 'kefu-message': KeFuMessageContentTypeEnum.TEXT === item.contentType }"
|
|
||||||
class="p-10px">
|
class="p-10px">
|
||||||
<!-- 文本消息 -->
|
<!-- 文本消息 -->
|
||||||
<MessageItem :message="item">
|
<MessageItem :message="item">
|
||||||
@ -44,15 +40,13 @@ v-if="item.senderType === UserTypeEnum.MEMBER" :src="conversation.userAvatar"
|
|||||||
</MessageItem>
|
</MessageItem>
|
||||||
<!-- 图片消息 -->
|
<!-- 图片消息 -->
|
||||||
<MessageItem :message="item">
|
<MessageItem :message="item">
|
||||||
<el-image
|
<el-image v-if="KeFuMessageContentTypeEnum.IMAGE === item.contentType"
|
||||||
v-if="KeFuMessageContentTypeEnum.IMAGE === item.contentType"
|
|
||||||
:initial-index="0" :preview-src-list="[item.content]" :src="item.content"
|
:initial-index="0" :preview-src-list="[item.content]" :src="item.content"
|
||||||
class="w-200px" fit="contain" preview-teleported />
|
class="w-200px" fit="contain" preview-teleported />
|
||||||
</MessageItem>
|
</MessageItem>
|
||||||
<!-- 商品消息 -->
|
<!-- 商品消息 -->
|
||||||
<MessageItem :message="item">
|
<MessageItem :message="item">
|
||||||
<ProductItem
|
<ProductItem v-if="KeFuMessageContentTypeEnum.PRODUCT === item.contentType"
|
||||||
v-if="KeFuMessageContentTypeEnum.PRODUCT === item.contentType"
|
|
||||||
:spuId="getMessageContent(item).spuId" :picUrl="getMessageContent(item).picUrl"
|
:spuId="getMessageContent(item).spuId" :picUrl="getMessageContent(item).picUrl"
|
||||||
:price="getMessageContent(item).price"
|
:price="getMessageContent(item).price"
|
||||||
:skuText="getMessageContent(item).introduction"
|
:skuText="getMessageContent(item).introduction"
|
||||||
@ -61,20 +55,17 @@ v-if="KeFuMessageContentTypeEnum.PRODUCT === item.contentType"
|
|||||||
</MessageItem>
|
</MessageItem>
|
||||||
<!-- 订单消息 -->
|
<!-- 订单消息 -->
|
||||||
<MessageItem :message="item">
|
<MessageItem :message="item">
|
||||||
<OrderItem
|
<OrderItem v-if="KeFuMessageContentTypeEnum.ORDER === item.contentType"
|
||||||
v-if="KeFuMessageContentTypeEnum.ORDER === item.contentType"
|
|
||||||
:message="item" class="max-w-100%" />
|
:message="item" class="max-w-100%" />
|
||||||
</MessageItem>
|
</MessageItem>
|
||||||
</div>
|
</div>
|
||||||
<el-avatar
|
<el-avatar v-if="item.senderType === UserTypeEnum.ADMIN" :src="item.senderAvatar"
|
||||||
v-if="item.senderType === UserTypeEnum.ADMIN" :src="item.senderAvatar"
|
|
||||||
alt="avatar" />
|
alt="avatar" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-scrollbar>
|
</el-scrollbar>
|
||||||
<div
|
<div v-show="showNewMessageTip" class="newMessageTip flex items-center cursor-pointer"
|
||||||
v-show="showNewMessageTip" class="newMessageTip flex items-center cursor-pointer"
|
|
||||||
@click="handleToNewMessage">
|
@click="handleToNewMessage">
|
||||||
<span>有新消息</span>
|
<span>有新消息</span>
|
||||||
<Icon class="ml-5px" icon="ep:bottom" />
|
<Icon class="ml-5px" icon="ep:bottom" />
|
||||||
@ -91,18 +82,21 @@ v-show="showNewMessageTip" class="newMessageTip flex items-center cursor-pointer
|
|||||||
<img :src="Picture" class="w-32px h-32px" @click="huashu" />
|
<img :src="Picture" class="w-32px h-32px" @click="huashu" />
|
||||||
</div>
|
</div>
|
||||||
<!-- 转接按钮 -->
|
<!-- 转接按钮 -->
|
||||||
<el-dropdown placement="top" style="margin-left: auto;margin-right: 15px; margin-top:5px;margin-top:5px;cursor: pointer;" ref="dropdown1" trigger="contextmenu">
|
<el-dropdown placement="top"
|
||||||
|
style="margin-left: auto;margin-right: 15px; margin-top:5px;margin-top:5px;cursor: pointer;"
|
||||||
|
ref="dropdown1" trigger="contextmenu">
|
||||||
<div>
|
<div>
|
||||||
<img :src="Picture2" class="w-27px h-27px" @click="getOnlineStaffList" title="转接"/>
|
<img :src="Picture2" class="w-27px h-27px" @click="getOnlineStaffList" title="转接" />
|
||||||
</div>
|
</div>
|
||||||
<template #dropdown>
|
<template #dropdown>
|
||||||
<el-dropdown-item v-for="staff in onlineStaffList" :key="staff.id" :disabled="staff.id===getStaffToken()" @click="transferConversion(staff.id)">
|
<el-dropdown-item v-for="staff in onlineStaffList" :key="staff.id"
|
||||||
|
:disabled="staff.id===getStaffToken()" @click="transferConversion(staff.id)">
|
||||||
{{ staff.name }}
|
{{ staff.name }}
|
||||||
</el-dropdown-item>
|
</el-dropdown-item>
|
||||||
</template>
|
</template>
|
||||||
</el-dropdown>
|
</el-dropdown>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<el-input v-model="message" :rows="6" style="border-style: none" type="textarea" />
|
<el-input v-model="message" :rows="6" style="border-style: none" type="textarea" />
|
||||||
<div class="h-45px flex justify-end">
|
<div class="h-45px flex justify-end">
|
||||||
@ -120,12 +114,15 @@ v-show="showNewMessageTip" class="newMessageTip flex items-center cursor-pointer
|
|||||||
<!-- 左边占 30% -->
|
<!-- 左边占 30% -->
|
||||||
<div style="flex: 0 0 20%; padding: 10px;">
|
<div style="flex: 0 0 20%; padding: 10px;">
|
||||||
<el-menu :default-active="targetMenuId">
|
<el-menu :default-active="targetMenuId">
|
||||||
<el-menu-item v-for="item in huashuType" :index="item.value" :key="item.value" @click="clickMenu(item.value)">{{item.label}}</el-menu-item>
|
<el-menu-item v-for="item in huashuType" :index="item.value" :key="item.value"
|
||||||
|
@click="clickMenu(item.value)">{{item.label}}</el-menu-item>
|
||||||
</el-menu>
|
</el-menu>
|
||||||
</div>
|
</div>
|
||||||
<!-- 右边占 70% -->
|
<!-- 右边占 70% -->
|
||||||
<div style="flex: 1; padding: 5px; overflow-y: auto; max-height: 400px;">
|
<div style="flex: 1; padding: 5px; overflow-y: auto; max-height: 400px;">
|
||||||
<p v-for="item in verbalTrickList" :key="item.id" style="font-size: 12px;cursor: pointer;transition: background-color 0.3s;" class="hover-shadow" @click="huashuClick(item.details)">
|
<p v-for="item in verbalTrickList" :key="item.id"
|
||||||
|
style="font-size: 12px;cursor: pointer;transition: background-color 0.3s;" class="hover-shadow"
|
||||||
|
@click="huashuClick(item.details)">
|
||||||
{{item.details}}
|
{{item.details}}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -155,11 +152,11 @@ v-show="showNewMessageTip" class="newMessageTip flex items-center cursor-pointer
|
|||||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||||
import { debounce } from 'lodash-es'
|
import { debounce } from 'lodash-es'
|
||||||
import { jsonParse } from '@/utils'
|
import { jsonParse } from '@/utils'
|
||||||
import { getStaffToken, setStaffToken} from '@/utils/auth'
|
import { getStaffToken, setStaffToken } from '@/utils/auth'
|
||||||
import type { DropdownInstance } from 'element-plus'
|
import type { DropdownInstance } from 'element-plus'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
|
||||||
import * as DictDataApi from '@/api/system/dict/dict.data'
|
import * as DictDataApi from '@/api/system/dict/dict.data'
|
||||||
const huashuType = ref<DictDataApi.DictDataVO[]>([]) //
|
const huashuType = ref<DictDataApi.DictDataVO[]>([]) //
|
||||||
const targetMenuId = ref('0')
|
const targetMenuId = ref('0')
|
||||||
@ -170,7 +167,7 @@ v-show="showNewMessageTip" class="newMessageTip flex items-center cursor-pointer
|
|||||||
let dialogVisible = ref(false) // 弹窗的是否展示
|
let dialogVisible = ref(false) // 弹窗的是否展示
|
||||||
const dialogTitle = ref('客服话术') // 客服话术库弹窗标题
|
const dialogTitle = ref('客服话术') // 客服话术库弹窗标题
|
||||||
// 话术库的数据
|
// 话术库的数据
|
||||||
const verbalTrickList = ref<VerbalTrickVO[]>([])
|
const verbalTrickList = ref<VerbalTrickVO[]>([])
|
||||||
const message = ref('') // 消息弹窗
|
const message = ref('') // 消息弹窗
|
||||||
const { replaceEmoji } = useEmoji()
|
const { replaceEmoji } = useEmoji()
|
||||||
const messageTool = useMessage()
|
const messageTool = useMessage()
|
||||||
@ -178,10 +175,10 @@ v-show="showNewMessageTip" class="newMessageTip flex items-center cursor-pointer
|
|||||||
const conversation = ref<KeFuConversationRespVO>({} as KeFuConversationRespVO) // 用户会话
|
const conversation = ref<KeFuConversationRespVO>({} as KeFuConversationRespVO) // 用户会话
|
||||||
const showNewMessageTip = ref(false) // 显示有新消息提示
|
const showNewMessageTip = ref(false) // 显示有新消息提示
|
||||||
import { SupportStaffApi, SupportStaffVO } from '@/api/mall/promotion/supportstaff' // 客服列表接口
|
import { SupportStaffApi, SupportStaffVO } from '@/api/mall/promotion/supportstaff' // 客服列表接口
|
||||||
import { KeFuConversationApi} from '@/api/mall/promotion/kefu/conversation'
|
import { KeFuConversationApi } from '@/api/mall/promotion/kefu/conversation'
|
||||||
import { number } from 'vue-types'
|
import { number } from 'vue-types'
|
||||||
const onlineStaffList = ref<SupportStaffVO[]>([]) // 在线客服列表的数据
|
const onlineStaffList = ref<SupportStaffVO[]>([]) // 在线客服列表的数据
|
||||||
|
|
||||||
const queryParams = reactive({
|
const queryParams = reactive({
|
||||||
pageNo: 1,
|
pageNo: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
@ -297,25 +294,25 @@ v-show="showNewMessageTip" class="newMessageTip flex items-center cursor-pointer
|
|||||||
//话术分类菜单
|
//话术分类菜单
|
||||||
const getHuaShuTypeList = async () => {
|
const getHuaShuTypeList = async () => {
|
||||||
const data = await DictDataApi.getHuaShuTypeList()
|
const data = await DictDataApi.getHuaShuTypeList()
|
||||||
huashuType.value = data
|
huashuType.value = data
|
||||||
}
|
}
|
||||||
//话术内容
|
//话术内容
|
||||||
const getVerbalTrickList = async (id: string) => {
|
const getVerbalTrickList = async (id : string) => {
|
||||||
const response = await VerbalTrickApi.getVerbalTrickList(id);
|
const response = await VerbalTrickApi.getVerbalTrickList(id);
|
||||||
verbalTrickList.value = response; // 将数据部分赋值给 verbalTrickList
|
verbalTrickList.value = response; // 将数据部分赋值给 verbalTrickList
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*选择话术库内容*/
|
/*选择话术库内容*/
|
||||||
const huashuClick = (content: string) => {
|
const huashuClick = (content : string) => {
|
||||||
message.value = content;
|
message.value = content;
|
||||||
dialogVisible.value = false;
|
dialogVisible.value = false;
|
||||||
}
|
}
|
||||||
const clickMenu = (id: string) => {
|
const clickMenu = (id : string) => {
|
||||||
console.log('1111111111',id)
|
console.log('1111111111', id)
|
||||||
getVerbalTrickList(id)
|
getVerbalTrickList(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/** 发送文本消息 */
|
/** 发送文本消息 */
|
||||||
const handleSendMessage = async () => {
|
const handleSendMessage = async () => {
|
||||||
@ -423,9 +420,9 @@ v-show="showNewMessageTip" class="newMessageTip flex items-center cursor-pointer
|
|||||||
} finally {
|
} finally {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 转接客服人员列表 id:会话id kefuId:客服人员id */
|
/** 转接客服人员列表 id:会话id kefuId:客服人员id */
|
||||||
const transferConversion = async (kefuId: number) => {
|
const transferConversion = async (kefuId : number) => {
|
||||||
try {
|
try {
|
||||||
await KeFuConversationApi.transferConversion(queryParams.conversationId, kefuId)
|
await KeFuConversationApi.transferConversion(queryParams.conversationId, kefuId)
|
||||||
} finally {
|
} finally {
|
||||||
@ -435,15 +432,17 @@ v-show="showNewMessageTip" class="newMessageTip flex items-center cursor-pointer
|
|||||||
/** 初始化 **/
|
/** 初始化 **/
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getHuaShuTypeList()
|
getHuaShuTypeList()
|
||||||
getVerbalTrickList(targetMenuId.value)
|
getVerbalTrickList(targetMenuId.value)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
.hover-shadow:hover {
|
.hover-shadow:hover {
|
||||||
background-color: lightgray; /* 可根据需要调整颜色 */
|
background-color: lightgray;
|
||||||
|
|
||||||
|
/* 可根据需要调整颜色 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.kefu {
|
.kefu {
|
||||||
&-title {
|
&-title {
|
||||||
border-bottom: #e4e0e0 solid 1px;
|
border-bottom: #e4e0e0 solid 1px;
|
||||||
@ -487,8 +486,8 @@ v-show="showNewMessageTip" class="newMessageTip flex items-center cursor-pointer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.ss-row-right {
|
.ss-row-right {
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<!-- 目录是不是叫 member 好点。然后这个组件是 MemberInfo,里面有浏览足迹 -->
|
<!-- 目录是不是叫 member 好点。然后这个组件是 MemberInfo,里面有浏览足迹 -->
|
||||||
<template>
|
<template>
|
||||||
<div v-show="!isEmpty(conversation)" class="kefu">
|
<div v-show="!isEmpty(conversation)" class="kefu">
|
||||||
<div class="header-title h-60px flex justify-center items-center">他的足迹</div>
|
<!-- <div class="header-title h-60px flex justify-center items-center">他的足迹</div> -->
|
||||||
<el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
|
<el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
|
||||||
<el-tab-pane label="最近浏览" name="a" />
|
<el-tab-pane label="最近浏览" name="a" />
|
||||||
<el-tab-pane label="订单列表" name="b" />
|
<el-tab-pane label="订单列表" name="b" />
|
||||||
|
@ -1,35 +1,99 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-row :gutter="10" style="display: flex; justify-content: center;">
|
<div>
|
||||||
<!-- 会话列表 -->
|
<!-- 新区域,放在头部 -->
|
||||||
<el-col :span="5" >
|
<el-row style="display: flex; justify-content: center;">
|
||||||
<ContentWrap>
|
|
||||||
<KeFuConversationList ref="keFuConversationRef" @change="handleChange" />
|
<el-col :span="24">
|
||||||
</ContentWrap>
|
<div style="width:100%;height:70px;background-color:#3c80ff;">
|
||||||
</el-col>
|
<el-row>
|
||||||
<!-- 会话详情(选中会话的消息列表) -->
|
<el-col :span="6">
|
||||||
<el-col :span="10">
|
<el-input
|
||||||
<ContentWrap>
|
style="width: 80%;margin-top: 20px;margin-left:10px;"
|
||||||
<KeFuMessageList ref="keFuChatBoxRef" @change="getConversationList" />
|
:suffix-icon="Search"
|
||||||
</ContentWrap>
|
/>
|
||||||
</el-col>
|
</el-col>
|
||||||
<!-- 会员足迹(选中会话的会员足迹) -->
|
|
||||||
<el-col :span="5">
|
<el-col :span="6">
|
||||||
<ContentWrap>
|
<span style="display: flex; margin-top: 15px;">
|
||||||
<MemberBrowsingHistory ref="memberBrowsingHistoryRef" />
|
<el-avatar
|
||||||
</ContentWrap>
|
src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"
|
||||||
</el-col>
|
/>
|
||||||
</el-row>
|
<span style="margin-left:5px;margin-top: 9px;">客服桃子</span>
|
||||||
|
<el-switch
|
||||||
|
style="margin-top: 4px;--el-switch-on-color: #13ce66; --el-switch-off-color: #b6bac1;"
|
||||||
|
v-model="value6"
|
||||||
|
class="ml-2"
|
||||||
|
width="60"
|
||||||
|
inline-prompt
|
||||||
|
active-text="在线"
|
||||||
|
inactive-text="下线"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="6">
|
||||||
|
<el-button size="small" round style="margin-top:23px;margin-left:75%">退出登录</el-button>
|
||||||
|
</el-col>
|
||||||
|
|
||||||
|
<el-col :span="6">
|
||||||
|
<el-menu
|
||||||
|
background-color="#3c80ff"
|
||||||
|
text-color="white"
|
||||||
|
active-text-color="white"
|
||||||
|
style="width:100%;display: flex;"
|
||||||
|
>
|
||||||
|
<el-menu-item style="width:33%;height:70px" index="1">客户信息</el-menu-item>
|
||||||
|
<el-menu-item style="width:33%;height:70px" index="2">交易订单</el-menu-item>
|
||||||
|
<el-menu-item style="width:34%;height:70px" index="3">商品信息</el-menu-item>
|
||||||
|
</el-menu>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- <div style="color: #ffffff;" class="header-title h-60px flex justify-center items-center">他的足迹</div> -->
|
||||||
|
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<!-- 原有的三个区域 -->
|
||||||
|
<el-row style="display: flex; justify-content: center;">
|
||||||
|
<!-- 会话列表 -->
|
||||||
|
<el-col :span="6">
|
||||||
|
<ContentWrap>
|
||||||
|
<KeFuConversationList ref="keFuConversationRef" @change="handleChange"/>
|
||||||
|
</ContentWrap>
|
||||||
|
</el-col>
|
||||||
|
|
||||||
|
<!-- 会话详情(选中会话的消息列表) -->
|
||||||
|
<el-col :span="12">
|
||||||
|
<ContentWrap>
|
||||||
|
<KeFuMessageList ref="keFuChatBoxRef" @change="getConversationList"/>
|
||||||
|
</ContentWrap>
|
||||||
|
</el-col>
|
||||||
|
|
||||||
|
<!-- 会员足迹(选中会话的会员足迹) -->
|
||||||
|
<el-col :span="6">
|
||||||
|
<ContentWrap>
|
||||||
|
<MemberBrowsingHistory ref="memberBrowsingHistoryRef"/>
|
||||||
|
</ContentWrap>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { KeFuConversationList, KeFuMessageList, MemberBrowsingHistory } from './components'
|
import {KeFuConversationList, KeFuMessageList, MemberBrowsingHistory} from './components'
|
||||||
import { WebSocketMessageTypeConstants } from './components/tools/constants'
|
import {WebSocketMessageTypeConstants} from './components/tools/constants'
|
||||||
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'
|
||||||
|
|
||||||
defineOptions({ name: 'KeFu' })
|
defineOptions({name: 'KeFu'})
|
||||||
|
|
||||||
|
const value6 = ref(true)
|
||||||
|
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
@ -41,7 +105,7 @@
|
|||||||
) // WebSocket 服务地址
|
) // WebSocket 服务地址
|
||||||
|
|
||||||
/** 发起 WebSocket 连接 */
|
/** 发起 WebSocket 连接 */
|
||||||
const { data, close, open } = useWebSocket(server.value, {
|
const {data, close, open} = useWebSocket(server.value, {
|
||||||
autoReconnect: false,
|
autoReconnect: false,
|
||||||
heartbeat: true
|
heartbeat: true
|
||||||
})
|
})
|
||||||
@ -116,6 +180,7 @@
|
|||||||
.kefu {
|
.kefu {
|
||||||
height: calc(100vh - 165px);
|
height: calc(100vh - 165px);
|
||||||
overflow: auto; /* 确保内容可滚动 */
|
overflow: auto; /* 确保内容可滚动 */
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 定义滚动条样式 */
|
/* 定义滚动条样式 */
|
||||||
@ -133,8 +198,10 @@
|
|||||||
|
|
||||||
/* 定义滑块 内阴影+圆角 */
|
/* 定义滑块 内阴影+圆角 */
|
||||||
::-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>
|
||||||
|
@ -5,6 +5,8 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
|||||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||||
import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
|
import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
|
||||||
|
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
|
||||||
|
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
|
||||||
import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessageRespVO;
|
import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessageRespVO;
|
||||||
import cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.message.AppKeFuMessagePageReqVO;
|
import cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.message.AppKeFuMessagePageReqVO;
|
||||||
import cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.message.AppKeFuMessageSendReqVO;
|
import cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.message.AppKeFuMessageSendReqVO;
|
||||||
@ -31,6 +33,10 @@ public class AppKeFuMessageController {
|
|||||||
@Resource
|
@Resource
|
||||||
private KeFuMessageService kefuMessageService;
|
private KeFuMessageService kefuMessageService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private MemberUserApi memberUserApi;
|
||||||
|
|
||||||
|
|
||||||
@PostMapping("/send")
|
@PostMapping("/send")
|
||||||
@Operation(summary = "发送客服消息")
|
@Operation(summary = "发送客服消息")
|
||||||
@PreAuthenticated
|
@PreAuthenticated
|
||||||
@ -53,6 +59,19 @@ public class AppKeFuMessageController {
|
|||||||
@PreAuthenticated
|
@PreAuthenticated
|
||||||
public CommonResult<PageResult<KeFuMessageRespVO>> getKefuMessagePage(@Valid AppKeFuMessagePageReqVO pageReqVO) {
|
public CommonResult<PageResult<KeFuMessageRespVO>> getKefuMessagePage(@Valid AppKeFuMessagePageReqVO pageReqVO) {
|
||||||
PageResult<KeFuMessageDO> pageResult = kefuMessageService.getKeFuMessagePage(pageReqVO, getLoginUserId());
|
PageResult<KeFuMessageDO> pageResult = kefuMessageService.getKeFuMessagePage(pageReqVO, getLoginUserId());
|
||||||
|
for (int i = 0; i < pageResult.getList().size(); i++) {
|
||||||
|
KeFuMessageDO keFuMessageDO = pageResult.getList().get(i);
|
||||||
|
if (keFuMessageDO.getSenderType() == 1){
|
||||||
|
MemberUserRespDTO user = memberUserApi.getUser(keFuMessageDO.getSenderId());
|
||||||
|
keFuMessageDO.setSenderAvatar(user.getAvatar());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keFuMessageDO.getSenderType() == 2){
|
||||||
|
String systemUserAvatar = kefuMessageService.findSystemUserAvatar(keFuMessageDO.getSenderId());
|
||||||
|
keFuMessageDO.setSenderAvatar(systemUserAvatar);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
return success(BeanUtils.toBean(pageResult, KeFuMessageRespVO.class));
|
return success(BeanUtils.toBean(pageResult, KeFuMessageRespVO.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
|
|||||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||||
import cn.iocoder.yudao.module.promotion.enums.kefu.KeFuMessageContentTypeEnum;
|
import cn.iocoder.yudao.module.promotion.enums.kefu.KeFuMessageContentTypeEnum;
|
||||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
import lombok.*;
|
import lombok.*;
|
||||||
@ -78,4 +79,7 @@ public class KeFuMessageDO extends BaseDO {
|
|||||||
*/
|
*/
|
||||||
private Boolean readStatus;
|
private Boolean readStatus;
|
||||||
|
|
||||||
|
|
||||||
|
@TableField(exist = false)
|
||||||
|
private String senderAvatar;
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuMessageDO;
|
|||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Select;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -46,4 +47,8 @@ public interface KeFuMessageMapper extends BaseMapperX<KeFuMessageDO> {
|
|||||||
.orderByDesc(KeFuMessageDO::getCreateTime));
|
.orderByDesc(KeFuMessageDO::getCreateTime));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Select(" SELECT avatar FROM system_users where id = #{id} ")
|
||||||
|
String findSystemUserAvatar(Long id);
|
||||||
|
|
||||||
}
|
}
|
@ -59,4 +59,6 @@ public interface KeFuMessageService {
|
|||||||
*/
|
*/
|
||||||
PageResult<KeFuMessageDO> getKeFuMessagePage(AppKeFuMessagePageReqVO pageReqVO, Long userId);
|
PageResult<KeFuMessageDO> getKeFuMessagePage(AppKeFuMessagePageReqVO pageReqVO, Long userId);
|
||||||
|
|
||||||
|
String findSystemUserAvatar(Long id);
|
||||||
|
|
||||||
}
|
}
|
@ -159,6 +159,11 @@ public class KeFuMessageServiceImpl implements KeFuMessageService {
|
|||||||
return keFuMessageMapper.selectPage(pageReqVO);
|
return keFuMessageMapper.selectPage(pageReqVO);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String findSystemUserAvatar(Long id) {
|
||||||
|
return keFuMessageMapper.findSystemUserAvatar(id);
|
||||||
|
}
|
||||||
|
|
||||||
private KeFuMessageServiceImpl getSelf() {
|
private KeFuMessageServiceImpl getSelf() {
|
||||||
return SpringUtil.getBean(getClass());
|
return SpringUtil.getBean(getClass());
|
||||||
}
|
}
|
||||||
|
@ -254,6 +254,7 @@ public class SocialClientServiceImpl implements SocialClientService {
|
|||||||
ObjUtil.defaultIfNull(reqVO.getAutoColor(), SocialWxQrcodeReqDTO.AUTO_COLOR),
|
ObjUtil.defaultIfNull(reqVO.getAutoColor(), SocialWxQrcodeReqDTO.AUTO_COLOR),
|
||||||
null,
|
null,
|
||||||
ObjUtil.defaultIfNull(reqVO.getHyaline(), SocialWxQrcodeReqDTO.HYALINE));
|
ObjUtil.defaultIfNull(reqVO.getHyaline(), SocialWxQrcodeReqDTO.HYALINE));
|
||||||
|
|
||||||
} catch (WxErrorException e) {
|
} catch (WxErrorException e) {
|
||||||
log.error("[getWxQrcode][reqVO({})) 获得小程序码失败]", reqVO, e);
|
log.error("[getWxQrcode][reqVO({})) 获得小程序码失败]", reqVO, e);
|
||||||
throw exception(SOCIAL_CLIENT_WEIXIN_MINI_APP_QRCODE_ERROR);
|
throw exception(SOCIAL_CLIENT_WEIXIN_MINI_APP_QRCODE_ERROR);
|
||||||
|
@ -57,7 +57,7 @@ spring:
|
|||||||
# url: jdbc:kingbase8://127.0.0.1:54321/test # 人大金仓 KingbaseES 连接的示例
|
# url: jdbc:kingbase8://127.0.0.1:54321/test # 人大金仓 KingbaseES 连接的示例
|
||||||
# url: jdbc:postgresql://127.0.0.1:5432/postgres # OpenGauss 连接的示例
|
# url: jdbc:postgresql://127.0.0.1:5432/postgres # OpenGauss 连接的示例
|
||||||
username: root
|
username: root
|
||||||
# password: 123456
|
# password: 123456
|
||||||
password: xpower1234
|
password: xpower1234
|
||||||
# username: sa # SQL Server 连接的示例
|
# username: sa # SQL Server 连接的示例
|
||||||
# password: Yudao@2024 # SQL Server 连接的示例
|
# password: Yudao@2024 # SQL Server 连接的示例
|
||||||
@ -228,7 +228,7 @@ yudao:
|
|||||||
enable: false
|
enable: false
|
||||||
demo: false # 关闭演示模式
|
demo: false # 关闭演示模式
|
||||||
wxa-code:
|
wxa-code:
|
||||||
env-version: develop # 小程序版本: 正式版为 "release";体验版为 "trial";开发版为 "develop"
|
env-version: release # 小程序版本: 正式版为 "release";体验版为 "trial";开发版为 "develop"
|
||||||
tencent-lbs-key: TVDBZ-TDILD-4ON4B-PFDZA-RNLKH-VVF6E # QQ 地图的密钥 https://lbs.qq.com/service/staticV2/staticGuide/staticDoc
|
tencent-lbs-key: TVDBZ-TDILD-4ON4B-PFDZA-RNLKH-VVF6E # QQ 地图的密钥 https://lbs.qq.com/service/staticV2/staticGuide/staticDoc
|
||||||
|
|
||||||
justauth:
|
justauth:
|
||||||
|
Loading…
Reference in New Issue
Block a user