Merge pull request '优化客服功能' (#106) from sjy-two into master
All checks were successful
continuous-integration/drone Build is passing
All checks were successful
continuous-integration/drone Build is passing
Reviewed-on: #106
This commit is contained in:
commit
5e56c2ddd1
@ -20,6 +20,7 @@ export interface UserVO {
|
||||
point: number | undefined | null
|
||||
totalPoint: number | undefined | null
|
||||
experience: number | null | undefined
|
||||
groupName: string
|
||||
}
|
||||
|
||||
// 查询会员用户列表
|
||||
@ -51,3 +52,8 @@ export const updateUserPoint = async (data: any) => {
|
||||
export const updateUserBalance = async (data: any) => {
|
||||
return await request.put({ url: `/member/user/update-balance`, data })
|
||||
}
|
||||
|
||||
// 客服查询用户详情
|
||||
export const getUserInfo = async (id: number) => {
|
||||
return await request.get({ url: `/member/user/getUserInfo?id=` + id })
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ export default defineComponent({
|
||||
onClick={handleClickOutside}
|
||||
></div>
|
||||
) : undefined}
|
||||
|
||||
|
||||
{renderLayout()}
|
||||
|
||||
<Backtop></Backtop>
|
||||
|
@ -35,88 +35,120 @@ const mobile = computed(() => appStore.getMobile)
|
||||
// 固定菜单
|
||||
const fixedMenu = computed(() => appStore.getFixedMenu)
|
||||
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
export const useRenderLayout = () => {
|
||||
const renderClassic = () => {
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
class={[
|
||||
'absolute top-0 left-0 h-full layout-border__right',
|
||||
{ '!fixed z-3000': mobile.value }
|
||||
]}
|
||||
>
|
||||
{logo.value ? (
|
||||
<Logo
|
||||
class={[
|
||||
'bg-[var(--left-menu-bg-color)] relative',
|
||||
{
|
||||
'!pl-0': mobile.value && collapse.value,
|
||||
'w-[var(--left-menu-min-width)]': appStore.getCollapse,
|
||||
'w-[var(--left-menu-max-width)]': !appStore.getCollapse
|
||||
}
|
||||
]}
|
||||
style="transition: all var(--transition-time-02);"
|
||||
></Logo>
|
||||
) : undefined}
|
||||
<Menu class={[{ '!h-[calc(100%-var(--logo-height))]': logo.value }]}></Menu>
|
||||
</div>
|
||||
<div
|
||||
class={[
|
||||
`${prefixCls}-content`,
|
||||
'absolute top-0 h-[100%]',
|
||||
{
|
||||
'w-[calc(100%-var(--left-menu-min-width))] left-[var(--left-menu-min-width)]':
|
||||
collapse.value && !mobile.value && !mobile.value,
|
||||
'w-[calc(100%-var(--left-menu-max-width))] left-[var(--left-menu-max-width)]':
|
||||
!collapse.value && !mobile.value && !mobile.value,
|
||||
'fixed !w-full !left-0': mobile.value
|
||||
}
|
||||
]}
|
||||
style="transition: all var(--transition-time-02);"
|
||||
>
|
||||
<ElScrollbar
|
||||
v-loading={pageLoading.value}
|
||||
class={[
|
||||
`${prefixCls}-content-scrollbar`,
|
||||
{
|
||||
'!h-[calc(100%-var(--top-tool-height)-var(--tags-view-height))] mt-[calc(var(--top-tool-height)+var(--tags-view-height))]':
|
||||
fixedHeader.value
|
||||
}
|
||||
]}
|
||||
>
|
||||
<div
|
||||
class={[
|
||||
{
|
||||
'fixed top-0 left-0 z-10': fixedHeader.value,
|
||||
'w-[calc(100%-var(--left-menu-min-width))] !left-[var(--left-menu-min-width)]':
|
||||
collapse.value && fixedHeader.value && !mobile.value,
|
||||
'w-[calc(100%-var(--left-menu-max-width))] !left-[var(--left-menu-max-width)]':
|
||||
!collapse.value && fixedHeader.value && !mobile.value,
|
||||
'!w-full !left-0': mobile.value
|
||||
}
|
||||
]}
|
||||
style="transition: all var(--transition-time-02);"
|
||||
>
|
||||
<ToolHeader
|
||||
class={[
|
||||
'bg-[var(--top-header-bg-color)]',
|
||||
{
|
||||
'layout-border__bottom': !tagsView.value
|
||||
}
|
||||
]}
|
||||
></ToolHeader>
|
||||
|
||||
{tagsView.value ? (
|
||||
<TagsView class="layout-border__top layout-border__bottom"></TagsView>
|
||||
) : undefined}
|
||||
</div>
|
||||
|
||||
<AppView></AppView>
|
||||
</ElScrollbar>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
const route = useRoute()
|
||||
let renderClassic = null;
|
||||
if(route.path == "/kefu/kefu"){
|
||||
renderClassic = () => {
|
||||
return (
|
||||
<>
|
||||
|
||||
<div
|
||||
|
||||
style="transition: all var(--transition-time-02);width:85%;margin:0 auto;"
|
||||
>
|
||||
<ElScrollbar
|
||||
v-loading={pageLoading.value}
|
||||
class={[
|
||||
`${prefixCls}-content-scrollbar`,
|
||||
{
|
||||
|
||||
}
|
||||
]}
|
||||
>
|
||||
|
||||
|
||||
<AppView></AppView>
|
||||
</ElScrollbar>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}else {
|
||||
renderClassic = () => {
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
class={[
|
||||
'absolute top-0 left-0 h-full layout-border__right',
|
||||
{ '!fixed z-3000': mobile.value }
|
||||
]}
|
||||
>
|
||||
{logo.value ? (
|
||||
<Logo
|
||||
class={[
|
||||
'bg-[var(--left-menu-bg-color)] relative',
|
||||
{
|
||||
'!pl-0': mobile.value && collapse.value,
|
||||
'w-[var(--left-menu-min-width)]': appStore.getCollapse,
|
||||
'w-[var(--left-menu-max-width)]': !appStore.getCollapse
|
||||
}
|
||||
]}
|
||||
style="transition: all var(--transition-time-02);"
|
||||
></Logo>
|
||||
) : undefined}
|
||||
<Menu class={[{ '!h-[calc(100%-var(--logo-height))]': logo.value }]}></Menu>
|
||||
</div>
|
||||
<div
|
||||
class={[
|
||||
`${prefixCls}-content`,
|
||||
'absolute top-0 h-[100%]',
|
||||
{
|
||||
'w-[calc(100%-var(--left-menu-min-width))] left-[var(--left-menu-min-width)]':
|
||||
collapse.value && !mobile.value && !mobile.value,
|
||||
'w-[calc(100%-var(--left-menu-max-width))] left-[var(--left-menu-max-width)]':
|
||||
!collapse.value && !mobile.value && !mobile.value,
|
||||
'fixed !w-full !left-0': mobile.value
|
||||
}
|
||||
]}
|
||||
style="transition: all var(--transition-time-02);"
|
||||
>
|
||||
<ElScrollbar
|
||||
v-loading={pageLoading.value}
|
||||
class={[
|
||||
`${prefixCls}-content-scrollbar`,
|
||||
{
|
||||
'!h-[calc(100%-var(--top-tool-height)-var(--tags-view-height))] mt-[calc(var(--top-tool-height)+var(--tags-view-height))]':
|
||||
fixedHeader.value
|
||||
}
|
||||
]}
|
||||
>
|
||||
<div
|
||||
class={[
|
||||
{
|
||||
'fixed top-0 left-0 z-10': fixedHeader.value,
|
||||
'w-[calc(100%-var(--left-menu-min-width))] !left-[var(--left-menu-min-width)]':
|
||||
collapse.value && fixedHeader.value && !mobile.value,
|
||||
'w-[calc(100%-var(--left-menu-max-width))] !left-[var(--left-menu-max-width)]':
|
||||
!collapse.value && fixedHeader.value && !mobile.value,
|
||||
'!w-full !left-0': mobile.value
|
||||
}
|
||||
]}
|
||||
style="transition: all var(--transition-time-02);"
|
||||
>
|
||||
<ToolHeader
|
||||
class={[
|
||||
'bg-[var(--top-header-bg-color)]',
|
||||
{
|
||||
'layout-border__bottom': !tagsView.value
|
||||
}
|
||||
]}
|
||||
></ToolHeader>
|
||||
|
||||
{tagsView.value ? (
|
||||
<TagsView class="layout-border__top layout-border__bottom"></TagsView>
|
||||
) : undefined}
|
||||
</div>
|
||||
|
||||
<AppView></AppView>
|
||||
</ElScrollbar>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const renderTopLeft = () => {
|
||||
return (
|
||||
|
@ -1,11 +1,14 @@
|
||||
<template>
|
||||
<el-container v-if="showKeFuMessageList" class="kefu">
|
||||
<el-header>
|
||||
<!-- <el-header>
|
||||
<div class="kefu-title">{{ conversation.userNickname }}</div>
|
||||
</el-header>
|
||||
</el-header> -->
|
||||
<el-main class="kefu-content overflow-visible">
|
||||
<el-scrollbar ref="scrollbarRef" always height="calc(100vh - 495px)" @scroll="handleScroll">
|
||||
<el-scrollbar ref="scrollbarRef" always height="calc(100vh - 420px)" @scroll="handleScroll">
|
||||
<div v-if="refreshContent" ref="innerRef" class="w-[100%] pb-3px">
|
||||
|
||||
|
||||
|
||||
<!-- 消息列表 -->
|
||||
<div v-for="(item, index) in getMessageList0" :key="item.id" class="w-[100%]">
|
||||
<div class="flex justify-center items-center mb-20px">
|
||||
@ -20,6 +23,8 @@
|
||||
{{ item.content }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div :class="[
|
||||
item.senderType === UserTypeEnum.MEMBER
|
||||
? `ss-row-left`
|
||||
@ -63,6 +68,10 @@
|
||||
alt="avatar" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
<div v-show="showNewMessageTip" class="newMessageTip flex items-center cursor-pointer"
|
||||
@ -78,15 +87,15 @@
|
||||
<PictureSelectUpload class="ml-15px mt-3px cursor-pointer" @send-picture="handleSendPicture" />
|
||||
<!-- <VerbalTrick class="ml-11px mt-5px cursor-pointer" /> -->
|
||||
<!-- 话术库 -->
|
||||
<div style="margin-left: 9px; margin-top:5px;cursor: pointer;">
|
||||
<img :src="Picture" class="w-32px h-32px" @click="huashu" />
|
||||
<div style="margin-left: 15px; margin-top:4px;cursor: pointer;">
|
||||
<img :src="xiaoxi" class="w-22px h-22px" @click="huashu" />
|
||||
</div>
|
||||
<!-- 转接按钮 -->
|
||||
<el-dropdown placement="top"
|
||||
style="margin-left: auto;margin-right: 15px; margin-top:5px;margin-top:5px;cursor: pointer;"
|
||||
style="margin-left: auto;margin-right: 15px;cursor: pointer;"
|
||||
ref="dropdown1" trigger="contextmenu">
|
||||
<div>
|
||||
<img :src="Picture2" class="w-27px h-27px" @click="getOnlineStaffList" title="转接" />
|
||||
<img :src="Picture2" class="w-62px h-20px" @click="getOnlineStaffList" title="转接" />
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-item v-for="staff in onlineStaffList" :key="staff.id"
|
||||
@ -140,7 +149,8 @@
|
||||
import PictureSelectUpload from './tools/PictureSelectUpload.vue'
|
||||
// import VerbalTrick from './tools/VerbalTrick.vue'
|
||||
import Picture from '@/views/mall/promotion/kefu/components/asserts/huashu.png'
|
||||
import Picture2 from '@/views/mall/promotion/kefu/components/asserts/zhuanjie.png'
|
||||
import xiaoxi from '@/views/mall/promotion/kefu/components/asserts/xiaoxi.png'
|
||||
import Picture2 from '@/views/mall/promotion/kefu/components/asserts/zj.png'
|
||||
import ProductItem from './message/ProductItem.vue'
|
||||
import OrderItem from './message/OrderItem.vue'
|
||||
import { Emoji, useEmoji } from './tools/emoji'
|
||||
@ -178,6 +188,8 @@
|
||||
import { KeFuConversationApi } from '@/api/mall/promotion/kefu/conversation'
|
||||
import { number } from 'vue-types'
|
||||
const onlineStaffList = ref<SupportStaffVO[]>([]) // 在线客服列表的数据
|
||||
const messages = useMessage() // 消息弹窗
|
||||
const kefuName = ref('')
|
||||
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
@ -250,6 +262,7 @@
|
||||
/** 获得新会话的消息列表 */
|
||||
// TODO @puhui999:可优化:可以考虑本地做每个会话的消息 list 缓存;然后点击切换时,读取缓存;然后异步获取新消息,merge 下;
|
||||
const getNewMessageList = async (val : KeFuConversationRespVO) => {
|
||||
// console.log('22222222',val)
|
||||
// 会话切换,重置相关参数
|
||||
queryParams.pageNo = 1
|
||||
messageList.value = []
|
||||
@ -309,7 +322,6 @@
|
||||
dialogVisible.value = false;
|
||||
}
|
||||
const clickMenu = (id : string) => {
|
||||
console.log('1111111111', id)
|
||||
getVerbalTrickList(id)
|
||||
}
|
||||
|
||||
@ -417,6 +429,7 @@
|
||||
status: 1,
|
||||
})
|
||||
onlineStaffList.value = data.list
|
||||
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
@ -424,7 +437,16 @@
|
||||
/** 转接客服人员列表 id:会话id kefuId:客服人员id */
|
||||
const transferConversion = async (kefuId : number) => {
|
||||
try {
|
||||
await KeFuConversationApi.transferConversion(queryParams.conversationId, kefuId)
|
||||
const re = await KeFuConversationApi.transferConversion(queryParams.conversationId, kefuId)
|
||||
kefuName.value = re
|
||||
// 2. 组织发送消息
|
||||
const msg = {
|
||||
conversationId: conversation.value.id,
|
||||
contentType: KeFuMessageContentTypeEnum.SYSTEM,
|
||||
content: '已为您转接至' + re,
|
||||
}
|
||||
await sendMessage(msg)
|
||||
messages.success('转接成功')
|
||||
} finally {
|
||||
// todo 刷新会话列表
|
||||
}
|
||||
|
@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<div style="height: 522px ;" ref="userInfoRef" >
|
||||
<div>
|
||||
<span style="display: flex;">
|
||||
<el-avatar
|
||||
style="border: 1px solid #f8f9ee;"
|
||||
src="https://zysc.fjptzykj.com:3000/shangcheng/29e07b7973621a1a9679744e18f2b4efd1fd50d857fe594879e813d67be3e2db.png"
|
||||
/>
|
||||
<span style="margin-left:5px;margin-top: 9px;">Marvin</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<el-divider style="border-color: #f5f5f5;margin-top: 15px;margin-bottom:15px;" />
|
||||
<div>
|
||||
<span style="color: #5d5d59;font-size: 13px ;">手机号</span><span style="margin-left: 47px;font-size: 14px ;">暂无</span>
|
||||
</div>
|
||||
<div style="margin-top: 5px;">
|
||||
<span style="color: #5d5d59;font-size: 13px ;">分组</span><span style="margin-left: 60px;font-size: 14px ;">D类客户</span>
|
||||
</div>
|
||||
<div style="margin-top: 5px;">
|
||||
<span style="color: #5d5d59;font-size: 13px ;">用户标签</span><span style="margin-left: 33px;font-size: 14px ;">小客户</span>
|
||||
</div>
|
||||
<el-divider style="border-color: #f5f5f5;margin-top: 15px;margin-bottom:15px;" />
|
||||
|
||||
<div>
|
||||
<span style="color: #5d5d59;font-size: 13px ;">用户等级</span><span style="margin-left: 35px;font-size: 14px ;">暂无</span>
|
||||
</div>
|
||||
<div style="margin-top: 5px;">
|
||||
<span style="color: #5d5d59;font-size: 13px ;">推荐人</span><span style="margin-left: 47px;font-size: 14px ;">客户</span>
|
||||
</div>
|
||||
<div style="margin-top: 5px;">
|
||||
<span style="color: #5d5d59;font-size: 13px ;">用户类型</span><span style="margin-left: 33px;font-size: 14px ;">小客户</span>
|
||||
</div>
|
||||
<div>
|
||||
<span style="color: #5d5d59;font-size: 13px ;">余额</span><span style="margin-left: 60px;font-size: 14px ;">暂无</span>
|
||||
</div>
|
||||
<div style="margin-top: 5px;">
|
||||
<span style="color: #5d5d59;font-size: 13px ;">推广员</span><span style="margin-left: 47px;font-size: 14px ;">客户</span>
|
||||
</div>
|
||||
<div style="margin-top: 5px;">
|
||||
<span style="color: #5d5d59;font-size: 13px ;">生日</span><span style="margin-left: 60px;font-size: 14px ;">小客户</span>
|
||||
</div>
|
||||
<el-divider style="border-color: #f5f5f5;margin-top: 15px;margin-bottom:15px;" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script >
|
||||
import * as UserApi from '@/api/member/user'
|
||||
|
||||
|
||||
// const user = ref<UserApi.UserVO>({} as UserApi.UserVO)
|
||||
|
||||
|
||||
// const getUserId = async (id: string) => {
|
||||
// user.value = await UserApi.getUserInfo(id)
|
||||
// }
|
||||
// defineExpose({ getUserId })
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
@ -1,5 +1,6 @@
|
||||
import KeFuConversationList from './KeFuConversationList.vue'
|
||||
import KeFuMessageList from './KeFuMessageList.vue'
|
||||
import MemberBrowsingHistory from './history/MemberBrowsingHistory.vue'
|
||||
import UserInfo from './UserInfo.vue'
|
||||
|
||||
export { KeFuConversationList, KeFuMessageList, MemberBrowsingHistory }
|
||||
export { KeFuConversationList, KeFuMessageList, MemberBrowsingHistory ,UserInfo}
|
||||
|
@ -2,7 +2,8 @@
|
||||
<template>
|
||||
<el-popover :width="500" placement="top" trigger="click">
|
||||
<template #reference>
|
||||
<Icon :size="30" class="ml-10px cursor-pointer" icon="twemoji:grinning-face" />
|
||||
<!-- <Icon :size="30" class="ml-10px cursor-pointer" icon="twemoji:grinning-face" /> -->
|
||||
<img :src="biaoqing" style="margin-left: 10px;" class="w-23px h-23px" />
|
||||
</template>
|
||||
<ElScrollbar height="300px">
|
||||
<ul class="ml-2 flex flex-wrap px-2">
|
||||
@ -26,8 +27,10 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
defineOptions({ name: 'EmojiSelectPopover' })
|
||||
import biaoqing from '@/views/mall/promotion/kefu/components/asserts/biaoqian.png'
|
||||
import { Emoji, useEmoji } from './emoji'
|
||||
|
||||
|
||||
const { getEmojiList } = useEmoji()
|
||||
const emojiList = computed(() => getEmojiList())
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
<!-- 图片选择 -->
|
||||
<template>
|
||||
<div>
|
||||
<img :src="Picture" class="w-35px h-35px" @click="selectAndUpload" />
|
||||
<img :src="tupian" class="w-23px h-23px" @click="selectAndUpload" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Picture from '@/views/mall/promotion/kefu/components/asserts/picture.svg'
|
||||
import tupian from '@/views/mall/promotion/kefu/components/asserts/tupian.png'
|
||||
import * as FileApi from '@/api/infra/file'
|
||||
|
||||
defineOptions({ name: 'PictureSelectUpload' })
|
||||
|
@ -16,10 +16,10 @@
|
||||
<el-col :span="6">
|
||||
<span style="display: flex; margin-top: 15px;">
|
||||
<el-avatar
|
||||
src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"
|
||||
:src="pic"
|
||||
/>
|
||||
<span style="margin-left:5px;margin-top: 9px;">客服桃子</span>
|
||||
<el-switch
|
||||
<span style="margin-left:5px;margin-top: 9px;">{{name}}</span>
|
||||
<!-- <el-switch
|
||||
style="margin-top: 4px;--el-switch-on-color: #13ce66; --el-switch-off-color: #b6bac1;"
|
||||
v-model="value6"
|
||||
class="ml-2"
|
||||
@ -27,11 +27,11 @@
|
||||
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-button @click="out" size="small" round style="margin-top:23px;margin-left:75%">退出</el-button>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="6">
|
||||
@ -41,14 +41,11 @@
|
||||
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-item @click="userInfo" style="width:33%;height:70px" index="1">客户信息</el-menu-item>
|
||||
<el-menu-item @click="zuoji" 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>
|
||||
|
||||
@ -74,9 +71,55 @@
|
||||
|
||||
<!-- 会员足迹(选中会话的会员足迹) -->
|
||||
<el-col :span="6">
|
||||
<ContentWrap>
|
||||
<ContentWrap v-show="chick == '2'">
|
||||
<MemberBrowsingHistory ref="memberBrowsingHistoryRef"/>
|
||||
</ContentWrap>
|
||||
|
||||
<ContentWrap v-show = "chick == '1'">
|
||||
<div style="height: 522px ;" >
|
||||
<div>
|
||||
<span style="display: flex;">
|
||||
<el-avatar
|
||||
style="border: 1px solid #f8f9ee;"
|
||||
:src="user.avatar"
|
||||
/>
|
||||
<span style="margin-left:5px;margin-top: 9px;">{{user.nickname}}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<el-divider style="border-color: #f5f5f5;margin-top: 15px;margin-bottom:15px;" />
|
||||
<div>
|
||||
<span style="color: #5d5d59;font-size: 13px ;">手机号</span><span style="margin-left: 47px;font-size: 14px ;">{{user.mobile}}</span>
|
||||
</div>
|
||||
<div style="margin-top: 5px;">
|
||||
<span style="color: #5d5d59;font-size: 13px ;">分组</span><span style="margin-left: 60px;font-size: 14px ;">{{user.groupName}}</span>
|
||||
</div>
|
||||
<div style="margin-top: 5px;">
|
||||
<span style="color: #5d5d59;font-size: 13px ;">用户标签</span><span style="margin-left: 33px;font-size: 14px ;">小客户</span>
|
||||
</div>
|
||||
<el-divider style="border-color: #f5f5f5;margin-top: 15px;margin-bottom:15px;" />
|
||||
|
||||
<div>
|
||||
<span style="color: #5d5d59;font-size: 13px ;">用户等级</span><span style="margin-left: 35px;font-size: 14px ;">{{user.levelName}}</span>
|
||||
</div>
|
||||
<div style="margin-top: 5px;">
|
||||
<span style="color: #5d5d59;font-size: 13px ;">推荐人</span><span style="margin-left: 47px;font-size: 14px ;">客户</span>
|
||||
</div>
|
||||
<div style="margin-top: 5px;">
|
||||
<span style="color: #5d5d59;font-size: 13px ;">用户类型</span><span style="margin-left: 33px;font-size: 14px ;">小客户</span>
|
||||
</div>
|
||||
<div style="margin-top: 5px;">
|
||||
<span style="color: #5d5d59;font-size: 13px ;">积分</span><span style="margin-left: 60px;font-size: 14px ;">{{user.point}}</span>
|
||||
</div>
|
||||
<div style="margin-top: 5px;">
|
||||
<span style="color: #5d5d59;font-size: 13px ;">推广员</span><span style="margin-left: 47px;font-size: 14px ;">客户</span>
|
||||
</div>
|
||||
<div style="margin-top: 5px;">
|
||||
<span style="color: #5d5d59;font-size: 13px ;">生日</span><span style="margin-left: 60px;font-size: 14px ;">{{user.birthday}}</span>
|
||||
</div>
|
||||
<el-divider style="border-color: #f5f5f5;margin-top: 15px;margin-bottom:15px;" />
|
||||
</div>
|
||||
</ContentWrap>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
@ -84,18 +127,30 @@
|
||||
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {KeFuConversationList, KeFuMessageList, MemberBrowsingHistory} from './components'
|
||||
import { KeFuConversationList, KeFuMessageList,MemberBrowsingHistory,UserInfo } from './components'
|
||||
import {WebSocketMessageTypeConstants} from './components/tools/constants'
|
||||
import {KeFuConversationRespVO} from '@/api/mall/promotion/kefu/conversation'
|
||||
import {getRefreshToken, getAccessToken} from '@/utils/auth'
|
||||
import {useWebSocket} from '@vueuse/core'
|
||||
import {Search} from '@element-plus/icons-vue'
|
||||
import * as UserApi from '@/api/member/user'
|
||||
|
||||
|
||||
|
||||
defineOptions({name: 'KeFu'})
|
||||
|
||||
const value6 = ref(true)
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const name = params.get('name');
|
||||
const pic = params.get('pic');
|
||||
const conversations = ref<KeFuConversationRespVO[]>([])
|
||||
// const userInfoRef = ref<InstanceType<typeof UserInfo>>()
|
||||
const user = ref<UserApi.UserVO>({} as UserApi.UserVO)
|
||||
const userId = ref(0)
|
||||
|
||||
|
||||
// ======================= WebSocket start =======================
|
||||
const server = ref(
|
||||
@ -104,6 +159,19 @@
|
||||
getRefreshToken() // 使用 getRefreshToken() 方法,而不使用 getAccessToken() 方法的原因:WebSocket 无法方便的刷新访问令牌
|
||||
) // WebSocket 服务地址
|
||||
|
||||
const chick = ref('2')
|
||||
const userInfo = async () =>{
|
||||
chick.value = '1'
|
||||
user.value = await UserApi.getUserInfo(userId.value)
|
||||
}
|
||||
const zuoji = () =>{
|
||||
chick.value = '2'
|
||||
// keFuChatBoxRef.value?.getNewMessageList(conversations.value)
|
||||
memberBrowsingHistoryRef.value?.initHistory(conversations.value)
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** 发起 WebSocket 连接 */
|
||||
const {data, close, open} = useWebSocket(server.value, {
|
||||
autoReconnect: false,
|
||||
@ -158,9 +226,17 @@
|
||||
const keFuChatBoxRef = ref<InstanceType<typeof KeFuMessageList>>()
|
||||
const memberBrowsingHistoryRef = ref<InstanceType<typeof MemberBrowsingHistory>>()
|
||||
const handleChange = (conversation: KeFuConversationRespVO) => {
|
||||
conversations.value = conversation
|
||||
chick.value = '2'
|
||||
userId.value = conversation.userId
|
||||
keFuChatBoxRef.value?.getNewMessageList(conversation)
|
||||
memberBrowsingHistoryRef.value?.initHistory(conversation)
|
||||
}
|
||||
|
||||
const out = () =>{
|
||||
window.close();
|
||||
// window.location.href = '/kefu/support-staff';
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
|
@ -35,15 +35,6 @@
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<!-- <el-form-item label="登录密码" prop="password">
|
||||
<el-input
|
||||
v-model="queryParams.password"
|
||||
placeholder="请输入登录密码"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item> -->
|
||||
<el-form-item label="客服状态" prop="status">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
@ -59,36 +50,6 @@
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<!-- <el-form-item label="手机订单管理" prop="orderManage">
|
||||
<el-select
|
||||
v-model="queryParams.orderManage"
|
||||
placeholder="请选择手机订单管理"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.KEFU_SUPPORT_STAFF_ORDER_MANAGE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item> -->
|
||||
<!-- <el-form-item label="订单通知" prop="orderInform">
|
||||
<el-select
|
||||
v-model="queryParams.orderInform"
|
||||
placeholder="请选择订单通知"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.KEFU_SUPPORT_STAFF_ORDER_INFORM)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item> -->
|
||||
<el-form-item label="创建时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
@ -143,22 +104,11 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="手机号码" align="center" prop="phone" />
|
||||
<el-table-column label="登录账号" align="center" prop="account" />
|
||||
<!-- <el-table-column label="登录密码" align="center" prop="password" /> -->
|
||||
<el-table-column label="客服状态" align="center" prop="status">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.KEFU_SUPPORT_STAFF_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- <el-table-column label="手机订单管理" align="center" prop="orderManage">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.KEFU_SUPPORT_STAFF_ORDER_MANAGE" :value="scope.row.orderManage" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="订单通知" align="center" prop="orderInform">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.KEFU_SUPPORT_STAFF_ORDER_INFORM" :value="scope.row.orderInform" />
|
||||
</template>
|
||||
</el-table-column> -->
|
||||
<el-table-column
|
||||
label="创建时间"
|
||||
align="center"
|
||||
@ -188,7 +138,7 @@
|
||||
v-if="scope.row.status == 1"
|
||||
link
|
||||
type="success"
|
||||
@click="handleEnterConsole(scope.row.id)"
|
||||
@click="handleEnterConsole(scope.row.id,scope.row.name,scope.row.pic)"
|
||||
>
|
||||
进入工作台
|
||||
</el-button>
|
||||
@ -206,6 +156,9 @@
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<SupportStaffForm ref="formRef" @success="getList" />
|
||||
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@ -215,6 +168,7 @@ import download from '@/utils/download'
|
||||
import { SupportStaffApi, SupportStaffVO } from '@/api/mall/promotion/supportstaff'
|
||||
import SupportStaffForm from './SupportStaffForm.vue'
|
||||
import { setStaffToken} from '@/utils/auth'
|
||||
import { createImageViewer } from '@/components/ImageViewer'
|
||||
/** 客服人员 列表 */
|
||||
defineOptions({ name: 'SupportStaff' })
|
||||
|
||||
@ -271,6 +225,13 @@ const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
const imagePreview = (imgUrl: string) => {
|
||||
createImageViewer({
|
||||
zIndex: 9999999,
|
||||
urlList: [imgUrl]
|
||||
})
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
@ -284,9 +245,10 @@ const handleDelete = async (id: number) => {
|
||||
} catch {}
|
||||
}
|
||||
/** 客服进入工作台 */
|
||||
const handleEnterConsole = async (id: number) => {
|
||||
const handleEnterConsole = async (id: number,name: string,pic: string) => {
|
||||
setStaffToken(id);
|
||||
window.open(`${window.location.origin}/kefu/kefu`, '_blank');
|
||||
const url = `${window.location.origin}/kefu/kefu?name=${encodeURIComponent(name)}&pic=${encodeURIComponent(pic)}`;
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
|
@ -84,9 +84,9 @@ public class KeFuConversationController {
|
||||
}
|
||||
@Operation(summary = "转接会话给指定客服")
|
||||
@GetMapping("/transfer/{id}/{kefuId}")
|
||||
public CommonResult<Boolean> transferConversation(@PathVariable("id") Long id, @PathVariable("kefuId") Long kefuId) {
|
||||
conversationService.transferConversation(id, kefuId);
|
||||
public CommonResult<String> transferConversation(@PathVariable("id") Long id, @PathVariable("kefuId") Long kefuId) {
|
||||
String name = conversationService.transferConversation(id, kefuId);
|
||||
// 处理逻辑
|
||||
return success(true);
|
||||
return success(name);
|
||||
}
|
||||
}
|
@ -19,13 +19,13 @@ public interface KeFuConversationMapper extends BaseMapperX<KeFuConversationDO>
|
||||
default List<KeFuConversationDO> selectConversationList() {
|
||||
return selectList(new LambdaQueryWrapperX<KeFuConversationDO>()
|
||||
.eq(KeFuConversationDO::getAdminDeleted, Boolean.FALSE)
|
||||
.orderByDesc(KeFuConversationDO::getCreateTime));
|
||||
.orderByDesc(KeFuConversationDO::getLastMessageTime));
|
||||
}
|
||||
default List<KeFuConversationDO> selectConversationList(Long kefuId) {
|
||||
return selectList(new LambdaQueryWrapperX<KeFuConversationDO>()
|
||||
.eq(KeFuConversationDO::getAdminDeleted, Boolean.FALSE)
|
||||
.eqIfPresent(KeFuConversationDO::getKefuId, kefuId)
|
||||
.orderByDesc(KeFuConversationDO::getCreateTime));
|
||||
.orderByDesc(KeFuConversationDO::getLastMessageTime));
|
||||
}
|
||||
|
||||
default void updateAdminUnreadMessageCountIncrement(Long id) {
|
||||
|
@ -102,5 +102,5 @@ public interface KeFuConversationService {
|
||||
* @param kefuId 客服id
|
||||
* @return void
|
||||
*/
|
||||
void transferConversation(Long id, Long kefuId);
|
||||
String transferConversation(Long id, Long kefuId);
|
||||
}
|
@ -5,7 +5,9 @@ import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
|
||||
import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.conversation.KeFuConversationUpdatePinnedReqVO;
|
||||
import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuConversationDO;
|
||||
import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuMessageDO;
|
||||
import cn.iocoder.yudao.module.promotion.dal.dataobject.supportstaff.SupportStaffDO;
|
||||
import cn.iocoder.yudao.module.promotion.dal.mysql.kefu.KeFuConversationMapper;
|
||||
import cn.iocoder.yudao.module.promotion.dal.mysql.supportstaff.SupportStaffMapper;
|
||||
import cn.iocoder.yudao.module.promotion.enums.kefu.KeFuMessageContentTypeEnum;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@ -30,6 +32,9 @@ public class KeFuConversationServiceImpl implements KeFuConversationService {
|
||||
@Resource
|
||||
private KeFuConversationMapper conversationMapper;
|
||||
|
||||
@Resource
|
||||
private SupportStaffMapper supportStaffMapper;
|
||||
|
||||
@Override
|
||||
public void deleteKefuConversation(Long id) {
|
||||
// 校验存在
|
||||
@ -126,11 +131,13 @@ public class KeFuConversationServiceImpl implements KeFuConversationService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transferConversation(Long id, Long kefuId) {
|
||||
public String transferConversation(Long id, Long kefuId) {
|
||||
KeFuConversationDO keFuConversationDO = new KeFuConversationDO();
|
||||
keFuConversationDO.setId(id);
|
||||
keFuConversationDO.setKefuId(kefuId);
|
||||
conversationMapper.updateById(keFuConversationDO);
|
||||
SupportStaffDO supportStaffDO = supportStaffMapper.selectById(kefuId);
|
||||
return supportStaffDO.getName();
|
||||
}
|
||||
|
||||
}
|
@ -17,7 +17,10 @@ import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelDO;
|
||||
import cn.iocoder.yudao.module.member.dal.dataobject.memberCode.MemberCodeDo;
|
||||
import cn.iocoder.yudao.module.member.dal.dataobject.tag.MemberTagDO;
|
||||
import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
|
||||
import cn.iocoder.yudao.module.member.dal.mysql.group.MemberGroupMapper;
|
||||
import cn.iocoder.yudao.module.member.dal.mysql.level.MemberLevelMapper;
|
||||
import cn.iocoder.yudao.module.member.dal.mysql.memberCode.MemberCodeMapper;
|
||||
import cn.iocoder.yudao.module.member.dal.mysql.tag.MemberTagMapper;
|
||||
import cn.iocoder.yudao.module.member.enums.point.MemberPointBizTypeEnum;
|
||||
import cn.iocoder.yudao.module.member.service.clubCard.ClubCardService;
|
||||
import cn.iocoder.yudao.module.member.service.group.MemberGroupService;
|
||||
@ -52,6 +55,15 @@ import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLogi
|
||||
@Validated
|
||||
public class MemberUserController {
|
||||
|
||||
@Resource
|
||||
private MemberLevelMapper memberLevelMapper;
|
||||
|
||||
@Resource
|
||||
private MemberGroupMapper memberGroupMapper;
|
||||
|
||||
@Resource
|
||||
private MemberTagMapper memberTagMapper;
|
||||
|
||||
@Resource
|
||||
private MemberUserService memberUserService;
|
||||
@Resource
|
||||
@ -169,4 +181,23 @@ public class MemberUserController {
|
||||
memberCodeMapper.insert(memberCodeDo);
|
||||
return success(uuid);
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/getUserInfo")
|
||||
public CommonResult<MemberUserDO> getUserInfo(Long id){
|
||||
MemberUserDO user = memberUserService.getUser(id);
|
||||
|
||||
if (user.getGroupId() != null){
|
||||
MemberGroupDO groupDO = memberGroupMapper.selectOne("id", user.getGroupId());
|
||||
user.setGroupName(groupDO.getName());
|
||||
}
|
||||
|
||||
if (user.getLevelId() != null && user.getLevelId() != 0){
|
||||
MemberLevelDO levelDO = memberLevelMapper.selectOne("id", user.getLevelId());
|
||||
user.setLevelName(levelDO.getName());
|
||||
}
|
||||
|
||||
return success(user);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -131,6 +131,9 @@ public class MemberUserDO extends TenantBaseDO {
|
||||
* 关联 {@link MemberLevelDO#getId()} 字段
|
||||
*/
|
||||
private Long levelId;
|
||||
|
||||
@TableField(exist = false)
|
||||
private String levelName;
|
||||
/**
|
||||
* 会员经验
|
||||
*/
|
||||
@ -142,6 +145,9 @@ public class MemberUserDO extends TenantBaseDO {
|
||||
*/
|
||||
private Long groupId;
|
||||
|
||||
@TableField(exist = false)
|
||||
private String groupName;
|
||||
|
||||
/**
|
||||
* 是否绑过卡,是否开通过会员(0:未开通,1:试用,2:有效期,3:永久,4:过期)
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user