Compare commits
18 Commits
aa3d6de962
...
67459fe554
Author | SHA1 | Date | |
---|---|---|---|
67459fe554 | |||
a521d1c674 | |||
783ab383ec | |||
21a0e43cc1 | |||
f8de72bc6a | |||
c58893d335 | |||
45b6bf142e | |||
6beabe3478 | |||
94d29f3cad | |||
69201dd39f | |||
ade1a54439 | |||
eac7fc313d | |||
e650bab63b | |||
0e37da8584 | |||
32a1f6b109 | |||
6b5ba72b0a | |||
8ab64a909a | |||
81be16bd6c |
@ -1,4 +1,5 @@
|
|||||||
import request from '@/config/axios'
|
import request from '@/config/axios'
|
||||||
|
import { number } from 'vue-types'
|
||||||
|
|
||||||
export interface KeFuConversationRespVO {
|
export interface KeFuConversationRespVO {
|
||||||
id: number // 编号
|
id: number // 编号
|
||||||
@ -21,6 +22,10 @@ export const KeFuConversationApi = {
|
|||||||
getConversationList: async () => {
|
getConversationList: async () => {
|
||||||
return await request.get({ url: '/promotion/kefu-conversation/list' })
|
return await request.get({ url: '/promotion/kefu-conversation/list' })
|
||||||
},
|
},
|
||||||
|
// 获得客服会话列表
|
||||||
|
getConversationListByKefuId: async (kefuId: number) => {
|
||||||
|
return await request.get({ url: '/promotion/kefu-conversation/list?kefuId=' + kefuId })
|
||||||
|
},
|
||||||
// 客服会话置顶
|
// 客服会话置顶
|
||||||
updateConversationPinned: async (data: any) => {
|
updateConversationPinned: async (data: any) => {
|
||||||
return await request.put({
|
return await request.put({
|
||||||
@ -30,6 +35,10 @@ export const KeFuConversationApi = {
|
|||||||
},
|
},
|
||||||
// 删除客服会话
|
// 删除客服会话
|
||||||
deleteConversation: async (id: number) => {
|
deleteConversation: async (id: number) => {
|
||||||
return await request.get({ url: '/promotion/kefu-conversation/delete?id' + id })
|
return await request.delete({ url: '/promotion/kefu-conversation/delete?id=' + id })
|
||||||
|
},
|
||||||
|
// 转接会话给其它客服
|
||||||
|
transferConversion: async (id: number, kefuId: number) => {
|
||||||
|
return await request.get({ url: `/promotion/kefu-conversation/transfer/${id}/${kefuId}`})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ export const SupportStaffApi = {
|
|||||||
return await request.get({ url: `/promotion/support-staff/get?id=` + id })
|
return await request.get({ url: `/promotion/support-staff/get?id=` + id })
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
// 新增客服人员
|
// 新增客服人员
|
||||||
createSupportStaff: async (data: SupportStaffVO) => {
|
createSupportStaff: async (data: SupportStaffVO) => {
|
||||||
return await request.post({ url: `/promotion/support-staff/create`, data })
|
return await request.post({ url: `/promotion/support-staff/create`, data })
|
||||||
|
@ -6,6 +6,7 @@ const { wsCache } = useCache()
|
|||||||
|
|
||||||
const AccessTokenKey = 'ACCESS_TOKEN'
|
const AccessTokenKey = 'ACCESS_TOKEN'
|
||||||
const RefreshTokenKey = 'REFRESH_TOKEN'
|
const RefreshTokenKey = 'REFRESH_TOKEN'
|
||||||
|
const StaffTokenKey = 'STAFF_TOKEN'
|
||||||
|
|
||||||
// 获取token
|
// 获取token
|
||||||
export const getAccessToken = () => {
|
export const getAccessToken = () => {
|
||||||
@ -30,6 +31,16 @@ export const removeToken = () => {
|
|||||||
wsCache.delete(RefreshTokenKey)
|
wsCache.delete(RefreshTokenKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取客服id TODO:后面可以完善为token
|
||||||
|
export const getStaffToken = () => {
|
||||||
|
// 此处与TokenKey相同,此写法解决初始化时Cookies中不存在TokenKey报错
|
||||||
|
return wsCache.get(StaffTokenKey) ? wsCache.get(StaffTokenKey) : wsCache.get('STAFF_TOKEN')
|
||||||
|
}
|
||||||
|
// 设置客服id TODO:后面可以完善为token
|
||||||
|
export const setStaffToken = (id: number) => {
|
||||||
|
wsCache.set(StaffTokenKey, id)
|
||||||
|
}
|
||||||
|
|
||||||
/** 格式化token(jwt格式) */
|
/** 格式化token(jwt格式) */
|
||||||
export const formatToken = (token: string): string => {
|
export const formatToken = (token: string): string => {
|
||||||
return 'Bearer ' + token
|
return 'Bearer ' + token
|
||||||
|
@ -74,6 +74,7 @@ import { useEmoji } from './tools/emoji'
|
|||||||
import { formatPast } from '@/utils/formatTime'
|
import { formatPast } from '@/utils/formatTime'
|
||||||
import { KeFuMessageContentTypeEnum } from './tools/constants'
|
import { KeFuMessageContentTypeEnum } from './tools/constants'
|
||||||
import { useAppStore } from '@/store/modules/app'
|
import { useAppStore } from '@/store/modules/app'
|
||||||
|
import { getStaffToken} from '@/utils/auth'
|
||||||
|
|
||||||
defineOptions({ name: 'KeFuConversationList' })
|
defineOptions({ name: 'KeFuConversationList' })
|
||||||
|
|
||||||
@ -86,7 +87,7 @@ const collapse = computed(() => appStore.getCollapse) // 折叠菜单
|
|||||||
|
|
||||||
/** 加载会话列表 */
|
/** 加载会话列表 */
|
||||||
const getConversationList = async () => {
|
const getConversationList = async () => {
|
||||||
const list = await KeFuConversationApi.getConversationList()
|
const list = await KeFuConversationApi.getConversationListByKefuId(getStaffToken())
|
||||||
list.sort((a: KeFuConversationRespVO, _) => (a.adminPinned ? -1 : 1))
|
list.sort((a: KeFuConversationRespVO, _) => (a.adminPinned ? -1 : 1))
|
||||||
conversationList.value = list
|
conversationList.value = list
|
||||||
}
|
}
|
||||||
@ -206,8 +207,9 @@ watch(showRightMenu, (val) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.active {
|
.active {
|
||||||
border-left: 5px #3271ff solid;
|
border-left: 5px #0256ff solid;
|
||||||
background-color: var(--login-bg-color);
|
// background-color: var(--login-bg-color);
|
||||||
|
background-color: #409eff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pinned {
|
.pinned {
|
||||||
|
@ -10,7 +10,8 @@
|
|||||||
<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 v-if="
|
<div
|
||||||
|
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) }}
|
||||||
@ -20,16 +21,19 @@
|
|||||||
{{ item.content }}
|
{{ item.content }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div :class="[
|
<div
|
||||||
|
: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 v-if="item.senderType === UserTypeEnum.MEMBER" :src="conversation.userAvatar"
|
<el-avatar
|
||||||
|
v-if="item.senderType === UserTypeEnum.MEMBER" :src="conversation.userAvatar"
|
||||||
alt="avatar" class="w-60px h-60px" />
|
alt="avatar" class="w-60px h-60px" />
|
||||||
<div :class="{ 'kefu-message': KeFuMessageContentTypeEnum.TEXT === item.contentType }"
|
<div
|
||||||
|
:class="{ 'kefu-message': KeFuMessageContentTypeEnum.TEXT === item.contentType }"
|
||||||
class="p-10px">
|
class="p-10px">
|
||||||
<!-- 文本消息 -->
|
<!-- 文本消息 -->
|
||||||
<MessageItem :message="item">
|
<MessageItem :message="item">
|
||||||
@ -40,13 +44,15 @@
|
|||||||
</MessageItem>
|
</MessageItem>
|
||||||
<!-- 图片消息 -->
|
<!-- 图片消息 -->
|
||||||
<MessageItem :message="item">
|
<MessageItem :message="item">
|
||||||
<el-image v-if="KeFuMessageContentTypeEnum.IMAGE === item.contentType"
|
<el-image
|
||||||
|
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 v-if="KeFuMessageContentTypeEnum.PRODUCT === item.contentType"
|
<ProductItem
|
||||||
|
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"
|
||||||
@ -55,17 +61,20 @@
|
|||||||
</MessageItem>
|
</MessageItem>
|
||||||
<!-- 订单消息 -->
|
<!-- 订单消息 -->
|
||||||
<MessageItem :message="item">
|
<MessageItem :message="item">
|
||||||
<OrderItem v-if="KeFuMessageContentTypeEnum.ORDER === item.contentType"
|
<OrderItem
|
||||||
|
v-if="KeFuMessageContentTypeEnum.ORDER === item.contentType"
|
||||||
:message="item" class="max-w-100%" />
|
:message="item" class="max-w-100%" />
|
||||||
</MessageItem>
|
</MessageItem>
|
||||||
</div>
|
</div>
|
||||||
<el-avatar v-if="item.senderType === UserTypeEnum.ADMIN" :src="item.senderAvatar"
|
<el-avatar
|
||||||
|
v-if="item.senderType === UserTypeEnum.ADMIN" :src="item.senderAvatar"
|
||||||
alt="avatar" />
|
alt="avatar" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-scrollbar>
|
</el-scrollbar>
|
||||||
<div v-show="showNewMessageTip" class="newMessageTip flex items-center cursor-pointer"
|
<div
|
||||||
|
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" />
|
||||||
@ -81,6 +90,19 @@
|
|||||||
<div style="margin-left: 9px; margin-top:5px;cursor: pointer;">
|
<div style="margin-left: 9px; margin-top:5px;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">
|
||||||
|
<div>
|
||||||
|
<img :src="Picture2" class="w-27px h-27px" @click="getOnlineStaffList" title="转接"/>
|
||||||
|
</div>
|
||||||
|
<template #dropdown>
|
||||||
|
<el-dropdown-item v-for="staff in onlineStaffList" :key="staff.id" :disabled="staff.id===getStaffToken()" @click="transferConversion(staff.id)">
|
||||||
|
{{ staff.name }}
|
||||||
|
</el-dropdown-item>
|
||||||
|
</template>
|
||||||
|
</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">
|
||||||
@ -119,6 +141,7 @@
|
|||||||
import PictureSelectUpload from './tools/PictureSelectUpload.vue'
|
import PictureSelectUpload from './tools/PictureSelectUpload.vue'
|
||||||
// import VerbalTrick from './tools/VerbalTrick.vue'
|
// import VerbalTrick from './tools/VerbalTrick.vue'
|
||||||
import Picture from '@/views/mall/promotion/kefu/components/asserts/huashu.png'
|
import Picture from '@/views/mall/promotion/kefu/components/asserts/huashu.png'
|
||||||
|
import Picture2 from '@/views/mall/promotion/kefu/components/asserts/zhuanjie.png'
|
||||||
import ProductItem from './message/ProductItem.vue'
|
import ProductItem from './message/ProductItem.vue'
|
||||||
import OrderItem from './message/OrderItem.vue'
|
import OrderItem from './message/OrderItem.vue'
|
||||||
import { Emoji, useEmoji } from './tools/emoji'
|
import { Emoji, useEmoji } from './tools/emoji'
|
||||||
@ -130,6 +153,9 @@
|
|||||||
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 type { DropdownInstance } from 'element-plus'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
dayjs.extend(relativeTime)
|
dayjs.extend(relativeTime)
|
||||||
|
|
||||||
@ -145,6 +171,10 @@
|
|||||||
const messageList = ref<KeFuMessageRespVO[]>([]) // 消息列表
|
const messageList = ref<KeFuMessageRespVO[]>([]) // 消息列表
|
||||||
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 { KeFuConversationApi} from '@/api/mall/promotion/kefu/conversation'
|
||||||
|
import { number } from 'vue-types'
|
||||||
|
const onlineStaffList = ref<SupportStaffVO[]>([]) // 在线客服列表的数据
|
||||||
const queryParams = reactive({
|
const queryParams = reactive({
|
||||||
pageNo: 1,
|
pageNo: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
@ -152,6 +182,7 @@
|
|||||||
})
|
})
|
||||||
const total = ref(0) // 消息总条数
|
const total = ref(0) // 消息总条数
|
||||||
const refreshContent = ref(false) // 内容刷新,主要解决会话消息页面高度不一致导致的滚动功能精度失效
|
const refreshContent = ref(false) // 内容刷新,主要解决会话消息页面高度不一致导致的滚动功能精度失效
|
||||||
|
const dropdown1 = ref<DropdownInstance>()
|
||||||
|
|
||||||
/** 获悉消息内容 */
|
/** 获悉消息内容 */
|
||||||
const getMessageContent = computed(() => (item : any) => jsonParse(item.content))
|
const getMessageContent = computed(() => (item : any) => jsonParse(item.content))
|
||||||
@ -360,8 +391,29 @@
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
/** 查询在线客服人员列表 */
|
||||||
|
const getOnlineStaffList = async () => {
|
||||||
|
if (!dropdown1.value) return
|
||||||
|
dropdown1.value.handleOpen()
|
||||||
|
try {
|
||||||
|
const data = await SupportStaffApi.getSupportStaffPage({
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
status: 1,
|
||||||
|
})
|
||||||
|
onlineStaffList.value = data.list
|
||||||
|
} finally {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 转接客服人员列表 id:会话id kefuId:客服人员id */
|
||||||
|
const transferConversion = async (kefuId: number) => {
|
||||||
|
try {
|
||||||
|
await KeFuConversationApi.transferConversion(queryParams.conversationId, kefuId)
|
||||||
|
} finally {
|
||||||
|
// todo 刷新会话列表
|
||||||
|
}
|
||||||
|
}
|
||||||
/** 初始化 **/
|
/** 初始化 **/
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getVerbalTrickList()
|
getVerbalTrickList()
|
||||||
|
Binary file not shown.
After Width: | Height: | Size: 8.3 KiB |
@ -153,9 +153,16 @@ const openDetail = (spuId: number) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.title-text {
|
.title-text {
|
||||||
font-size: 13px;
|
font-size: 12px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
|
display: -webkit-box; /* 对于支持的浏览器 */
|
||||||
|
-webkit-box-orient: vertical; /* 垂直排列 */
|
||||||
|
overflow: hidden; /* 隐藏超出部分 */
|
||||||
|
-webkit-line-clamp: 2; /* 限制为2行 */
|
||||||
|
text-overflow: ellipsis; /* 超出部分显示省略号 */
|
||||||
|
line-height: 1.5; /* 行高,可以根据需要调整 */
|
||||||
|
max-height: 3rem; /* 根据行高和行数设置最大高度,2行 * 行高 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.spec-text {
|
.spec-text {
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-row :gutter="10">
|
<el-row :gutter="10" style="display: flex; justify-content: center;">
|
||||||
<!-- 会话列表 -->
|
<!-- 会话列表 -->
|
||||||
<el-col :span="6">
|
<el-col :span="5" >
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<KeFuConversationList ref="keFuConversationRef" @change="handleChange" />
|
<KeFuConversationList ref="keFuConversationRef" @change="handleChange" />
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
</el-col>
|
</el-col>
|
||||||
<!-- 会话详情(选中会话的消息列表) -->
|
<!-- 会话详情(选中会话的消息列表) -->
|
||||||
<el-col :span="12">
|
<el-col :span="10">
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<KeFuMessageList ref="keFuChatBoxRef" @change="getConversationList" />
|
<KeFuMessageList ref="keFuChatBoxRef" @change="getConversationList" />
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
</el-col>
|
</el-col>
|
||||||
<!-- 会员足迹(选中会话的会员足迹) -->
|
<!-- 会员足迹(选中会话的会员足迹) -->
|
||||||
<el-col :span="6">
|
<el-col :span="5">
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<MemberBrowsingHistory ref="memberBrowsingHistoryRef" />
|
<MemberBrowsingHistory ref="memberBrowsingHistoryRef" />
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
@ -26,7 +26,7 @@
|
|||||||
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 } from '@/utils/auth'
|
import { getRefreshToken, getAccessToken } from '@/utils/auth'
|
||||||
import { useWebSocket } from '@vueuse/core'
|
import { useWebSocket } from '@vueuse/core'
|
||||||
|
|
||||||
defineOptions({ name: 'KeFu' })
|
defineOptions({ name: 'KeFu' })
|
||||||
@ -42,7 +42,7 @@
|
|||||||
|
|
||||||
/** 发起 WebSocket 连接 */
|
/** 发起 WebSocket 连接 */
|
||||||
const { data, close, open } = useWebSocket(server.value, {
|
const { data, close, open } = useWebSocket(server.value, {
|
||||||
autoReconnect: true,
|
autoReconnect: false,
|
||||||
heartbeat: true
|
heartbeat: true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -166,7 +166,7 @@
|
|||||||
:formatter="dateFormatter"
|
:formatter="dateFormatter"
|
||||||
width="180px"
|
width="180px"
|
||||||
/>
|
/>
|
||||||
<el-table-column label="操作" align="center">
|
<el-table-column fixed="right" label="操作" align="center" min-width="200">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button
|
<el-button
|
||||||
link
|
link
|
||||||
@ -184,6 +184,14 @@
|
|||||||
>
|
>
|
||||||
删除
|
删除
|
||||||
</el-button>
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
v-if="scope.row.status == 1"
|
||||||
|
link
|
||||||
|
type="success"
|
||||||
|
@click="handleEnterConsole(scope.row.id)"
|
||||||
|
>
|
||||||
|
进入工作台
|
||||||
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
@ -206,7 +214,7 @@ import { dateFormatter } from '@/utils/formatTime'
|
|||||||
import download from '@/utils/download'
|
import download from '@/utils/download'
|
||||||
import { SupportStaffApi, SupportStaffVO } from '@/api/mall/promotion/supportstaff'
|
import { SupportStaffApi, SupportStaffVO } from '@/api/mall/promotion/supportstaff'
|
||||||
import SupportStaffForm from './SupportStaffForm.vue'
|
import SupportStaffForm from './SupportStaffForm.vue'
|
||||||
|
import { setStaffToken} from '@/utils/auth'
|
||||||
/** 客服人员 列表 */
|
/** 客服人员 列表 */
|
||||||
defineOptions({ name: 'SupportStaff' })
|
defineOptions({ name: 'SupportStaff' })
|
||||||
|
|
||||||
@ -275,7 +283,11 @@ const handleDelete = async (id: number) => {
|
|||||||
await getList()
|
await getList()
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
/** 客服进入工作台 */
|
||||||
|
const handleEnterConsole = async (id: number) => {
|
||||||
|
setStaffToken(id);
|
||||||
|
window.open(`${window.location.origin}/kefu/kefu`, '_blank');
|
||||||
|
}
|
||||||
/** 导出按钮操作 */
|
/** 导出按钮操作 */
|
||||||
const handleExport = async () => {
|
const handleExport = async () => {
|
||||||
try {
|
try {
|
||||||
|
@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
|
|||||||
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
|
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
|
||||||
import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.conversation.KeFuConversationRespVO;
|
import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.conversation.KeFuConversationRespVO;
|
||||||
import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.conversation.KeFuConversationUpdatePinnedReqVO;
|
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.service.kefu.KeFuConversationService;
|
import cn.iocoder.yudao.module.promotion.service.kefu.KeFuConversationService;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
@ -16,6 +17,7 @@ import org.springframework.web.bind.annotation.*;
|
|||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import javax.validation.Valid;
|
import javax.validation.Valid;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@ -54,16 +56,37 @@ public class KeFuConversationController {
|
|||||||
@GetMapping("/list")
|
@GetMapping("/list")
|
||||||
@Operation(summary = "获得客服会话列表")
|
@Operation(summary = "获得客服会话列表")
|
||||||
@PreAuthorize("@ss.hasPermission('promotion:kefu-conversation:query')")
|
@PreAuthorize("@ss.hasPermission('promotion:kefu-conversation:query')")
|
||||||
public CommonResult<List<KeFuConversationRespVO>> getConversationList() {
|
public CommonResult<List<KeFuConversationRespVO>> getConversationList(@RequestParam(required = false, value = "kefuId") Long kefuId) {
|
||||||
// 查询会话列表
|
// 如果会话列表中的kefuId为null,则转接给当前kefuId(处理小程序新会话)
|
||||||
List<KeFuConversationRespVO> respList = BeanUtils.toBean(conversationService.getKefuConversationList(),
|
if (kefuId != null) {
|
||||||
|
List<KeFuConversationDO> updateList = new ArrayList<>();
|
||||||
|
List<KeFuConversationRespVO> allConversationList = BeanUtils.toBean(conversationService.getKefuConversationList(),
|
||||||
KeFuConversationRespVO.class);
|
KeFuConversationRespVO.class);
|
||||||
|
for (KeFuConversationRespVO keFuConversationRespVO : allConversationList) {
|
||||||
|
if (keFuConversationRespVO.getKefuId() == null) {
|
||||||
|
keFuConversationRespVO.setKefuId(kefuId);
|
||||||
|
updateList.add(BeanUtils.toBean(keFuConversationRespVO, KeFuConversationDO.class));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!updateList.isEmpty()) {
|
||||||
|
conversationService.updateConversation(updateList);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 查询会话列表
|
||||||
|
List<KeFuConversationRespVO> respList = BeanUtils.toBean(conversationService.getKefuConversationList(kefuId),
|
||||||
|
KeFuConversationRespVO.class);
|
||||||
// 拼接数据
|
// 拼接数据
|
||||||
Map<Long, MemberUserRespDTO> userMap = memberUserApi.getUserMap(convertSet(respList, KeFuConversationRespVO::getUserId));
|
Map<Long, MemberUserRespDTO> userMap = memberUserApi.getUserMap(convertSet(respList, KeFuConversationRespVO::getUserId));
|
||||||
respList.forEach(item-> findAndThen(userMap, item.getUserId(),
|
respList.forEach(item -> findAndThen(userMap, item.getUserId(),
|
||||||
memberUser-> item.setUserAvatar(memberUser.getAvatar()).setUserNickname(memberUser.getNickname())));
|
memberUser -> item.setUserAvatar(memberUser.getAvatar()).setUserNickname(memberUser.getNickname())));
|
||||||
return success(respList);
|
return success(respList);
|
||||||
}
|
}
|
||||||
|
@Operation(summary = "转接会话给指定客服")
|
||||||
|
@GetMapping("/transfer/{id}/{kefuId}")
|
||||||
|
public CommonResult<Boolean> transferConversation(@PathVariable("id") Long id, @PathVariable("kefuId") Long kefuId) {
|
||||||
|
conversationService.transferConversation(id, kefuId);
|
||||||
|
// 处理逻辑
|
||||||
|
return success(true);
|
||||||
|
}
|
||||||
}
|
}
|
@ -43,4 +43,7 @@ public class KeFuConversationRespVO {
|
|||||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
private LocalDateTime createTime;
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
@Schema(description = "所属客服编号")
|
||||||
|
private Long kefuId;
|
||||||
|
|
||||||
}
|
}
|
@ -80,4 +80,9 @@ public class KeFuConversationDO extends BaseDO {
|
|||||||
*/
|
*/
|
||||||
private Integer adminUnreadMessageCount;
|
private Integer adminUnreadMessageCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所属客服编号
|
||||||
|
*/
|
||||||
|
private Long kefuId;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,12 @@ public interface KeFuConversationMapper extends BaseMapperX<KeFuConversationDO>
|
|||||||
.eq(KeFuConversationDO::getAdminDeleted, Boolean.FALSE)
|
.eq(KeFuConversationDO::getAdminDeleted, Boolean.FALSE)
|
||||||
.orderByDesc(KeFuConversationDO::getCreateTime));
|
.orderByDesc(KeFuConversationDO::getCreateTime));
|
||||||
}
|
}
|
||||||
|
default List<KeFuConversationDO> selectConversationList(Long kefuId) {
|
||||||
|
return selectList(new LambdaQueryWrapperX<KeFuConversationDO>()
|
||||||
|
.eq(KeFuConversationDO::getAdminDeleted, Boolean.FALSE)
|
||||||
|
.eqIfPresent(KeFuConversationDO::getKefuId, kefuId)
|
||||||
|
.orderByDesc(KeFuConversationDO::getCreateTime));
|
||||||
|
}
|
||||||
|
|
||||||
default void updateAdminUnreadMessageCountIncrement(Long id) {
|
default void updateAdminUnreadMessageCountIncrement(Long id) {
|
||||||
update(new LambdaUpdateWrapper<KeFuConversationDO>()
|
update(new LambdaUpdateWrapper<KeFuConversationDO>()
|
||||||
|
@ -50,11 +50,18 @@ public interface KeFuConversationService {
|
|||||||
void updateConversationAdminDeleted(Long id, Boolean adminDeleted);
|
void updateConversationAdminDeleted(Long id, Boolean adminDeleted);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 【管理员】获得客服会话列表
|
* 【管理员】获得全部客服会话列表
|
||||||
*
|
*
|
||||||
* @return 会话列表
|
* @return 会话列表
|
||||||
*/
|
*/
|
||||||
List<KeFuConversationDO> getKefuConversationList();
|
List<KeFuConversationDO> getKefuConversationList();
|
||||||
|
/**
|
||||||
|
* 【管理员】获得客服会话列表
|
||||||
|
*
|
||||||
|
* @param kefuId 客服编号
|
||||||
|
* @return 会话列表
|
||||||
|
*/
|
||||||
|
List<KeFuConversationDO> getKefuConversationList(Long kefuId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 【会员】获得或创建会话
|
* 【会员】获得或创建会话
|
||||||
@ -82,4 +89,18 @@ public interface KeFuConversationService {
|
|||||||
*/
|
*/
|
||||||
KeFuConversationDO getConversationByUserId(Long userId);
|
KeFuConversationDO getConversationByUserId(Long userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新客服列表
|
||||||
|
* @param updateList
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
void updateConversation(List<KeFuConversationDO> updateList);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转接会话
|
||||||
|
* @param id 会话id
|
||||||
|
* @param kefuId 客服id
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
void transferConversation(Long id, Long kefuId);
|
||||||
}
|
}
|
@ -87,6 +87,11 @@ public class KeFuConversationServiceImpl implements KeFuConversationService {
|
|||||||
return conversationMapper.selectConversationList();
|
return conversationMapper.selectConversationList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<KeFuConversationDO> getKefuConversationList(Long kefuId) {
|
||||||
|
return conversationMapper.selectConversationList(kefuId);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public KeFuConversationDO getOrCreateConversation(Long userId) {
|
public KeFuConversationDO getOrCreateConversation(Long userId) {
|
||||||
KeFuConversationDO conversation = conversationMapper.selectOne(KeFuConversationDO::getUserId, userId);
|
KeFuConversationDO conversation = conversationMapper.selectOne(KeFuConversationDO::getUserId, userId);
|
||||||
@ -115,4 +120,17 @@ public class KeFuConversationServiceImpl implements KeFuConversationService {
|
|||||||
return conversationMapper.selectByUserId(userId);
|
return conversationMapper.selectByUserId(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateConversation(List<KeFuConversationDO> updateList) {
|
||||||
|
conversationMapper.updateBatch(updateList);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void transferConversation(Long id, Long kefuId) {
|
||||||
|
KeFuConversationDO keFuConversationDO = new KeFuConversationDO();
|
||||||
|
keFuConversationDO.setId(id);
|
||||||
|
keFuConversationDO.setKefuId(kefuId);
|
||||||
|
conversationMapper.updateById(keFuConversationDO);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -76,8 +76,10 @@ public class AppTradeOrderController {
|
|||||||
@Operation(summary = "更新订单为已支付") // 由 pay-module 支付服务,进行回调,可见 PayNotifyJob
|
@Operation(summary = "更新订单为已支付") // 由 pay-module 支付服务,进行回调,可见 PayNotifyJob
|
||||||
@PermitAll
|
@PermitAll
|
||||||
public CommonResult<Boolean> updateOrderPaid(@RequestBody PayOrderNotifyReqDTO notifyReqDTO) {
|
public CommonResult<Boolean> updateOrderPaid(@RequestBody PayOrderNotifyReqDTO notifyReqDTO) {
|
||||||
|
System.out.println("进入回调notify:"+ notifyReqDTO);
|
||||||
tradeOrderUpdateService.updateOrderPaid(Long.valueOf(notifyReqDTO.getMerchantOrderId()),
|
tradeOrderUpdateService.updateOrderPaid(Long.valueOf(notifyReqDTO.getMerchantOrderId()),
|
||||||
notifyReqDTO.getPayOrderId());
|
notifyReqDTO.getPayOrderId());
|
||||||
|
System.out.println("订单更新完成");
|
||||||
return success(true);
|
return success(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,6 +32,6 @@ public interface TradePaidMemberUserService {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
TradePaidMemberUserDO getByIdAndPayOrderId(Long id, Long payOrderId);
|
TradePaidMemberUserDO getByPayOrderId(Long payOrderId);
|
||||||
|
|
||||||
}
|
}
|
@ -85,10 +85,9 @@ public class TradeTradePaidMemberUserServiceImpl implements TradePaidMemberUserS
|
|||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TradePaidMemberUserDO getByIdAndPayOrderId(Long id, Long payOrderId) {
|
public TradePaidMemberUserDO getByPayOrderId(Long payOrderId) {
|
||||||
TradePaidMemberUserDO tradePaidMemberUserDO = tradePaidMemberUserMapper.selectOne(
|
TradePaidMemberUserDO tradePaidMemberUserDO = tradePaidMemberUserMapper.selectOne(
|
||||||
new LambdaQueryWrapperX<TradePaidMemberUserDO>().eqIfPresent(TradePaidMemberUserDO::getId,id)
|
new LambdaQueryWrapperX<TradePaidMemberUserDO>().eqIfPresent(TradePaidMemberUserDO::getPayOrderId,payOrderId));
|
||||||
.eqIfPresent(TradePaidMemberUserDO::getPayOrderId,payOrderId));
|
|
||||||
return tradePaidMemberUserDO;
|
return tradePaidMemberUserDO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -330,15 +330,17 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
|
|||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
@TradeOrderLog(operateType = TradeOrderOperateTypeEnum.MEMBER_PAY)
|
@TradeOrderLog(operateType = TradeOrderOperateTypeEnum.MEMBER_PAY)
|
||||||
public void updateOrderPaid(Long id, Long payOrderId) {
|
public void updateOrderPaid(Long id, Long payOrderId) {
|
||||||
|
System.out.println("进入校验");
|
||||||
// 1. 校验并获得交易订单(可支付)
|
// 1. 校验并获得交易订单(可支付)
|
||||||
KeyValue<TradeOrderDO, PayOrderRespDTO> orderResult = validateOrderPayable(id, payOrderId);
|
KeyValue<TradeOrderDO, PayOrderRespDTO> orderResult = validateOrderPayable(id, payOrderId);
|
||||||
|
System.out.println("校验成功");
|
||||||
TradeOrderDO order = orderResult.getKey();
|
TradeOrderDO order = orderResult.getKey();
|
||||||
PayOrderRespDTO payOrder = orderResult.getValue();
|
PayOrderRespDTO payOrder = orderResult.getValue();
|
||||||
System.out.println("交易订单成功id:"+id+"和payOrderId:"+payOrderId);
|
System.out.println("交易订单成功id:"+id+"和payOrderId:"+payOrderId);
|
||||||
//会员用户信息,支付状态修改
|
//会员用户信息,支付状态修改
|
||||||
TradeMemberUserDO tradeMemberUserDO = tradeMemberUserService.getUser(getLoginUserId());
|
TradeMemberUserDO tradeMemberUserDO = tradeMemberUserService.getUser(getLoginUserId());
|
||||||
TradePaidMemberUserDO tradePaidMemberUserDO = tradePaidMemberUserService.getByIdAndPayOrderId(id,payOrderId);
|
TradePaidMemberUserDO tradePaidMemberUserDO = tradePaidMemberUserService.getByPayOrderId(payOrderId);
|
||||||
if(tradePaidMemberUserDO != null && tradeMemberUserDO != null){
|
if(tradePaidMemberUserDO != null ){
|
||||||
System.out.println("进入会员支付修改操作paid:"+tradePaidMemberUserDO+"和member:"+tradeMemberUserDO);
|
System.out.println("进入会员支付修改操作paid:"+tradePaidMemberUserDO+"和member:"+tradeMemberUserDO);
|
||||||
tradePaidMemberUserDO.setPayTime(LocalDateTime.now());
|
tradePaidMemberUserDO.setPayTime(LocalDateTime.now());
|
||||||
tradePaidMemberUserDO.setPaid(true);
|
tradePaidMemberUserDO.setPaid(true);
|
||||||
|
@ -109,4 +109,6 @@ public class PaidMemberUserPageReqVO extends PageParam {
|
|||||||
@Schema(description = "会员类型")
|
@Schema(description = "会员类型")
|
||||||
private String payMemberType;
|
private String payMemberType;
|
||||||
|
|
||||||
|
@Schema(description = "商户订单编号")
|
||||||
|
private String merchantOrderId;
|
||||||
}
|
}
|
@ -127,4 +127,7 @@ public class PaidMemberUserRespVO {
|
|||||||
@Schema(description = "会员类型")
|
@Schema(description = "会员类型")
|
||||||
private String payMemberType;
|
private String payMemberType;
|
||||||
|
|
||||||
|
@Schema(description = "商户订单编号")
|
||||||
|
private String merchantOrderId;
|
||||||
|
|
||||||
}
|
}
|
@ -106,4 +106,7 @@ public class PaidMemberUserSaveReqVO {
|
|||||||
|
|
||||||
@Schema(description = "会员类型", requiredMode = Schema.RequiredMode.REQUIRED)
|
@Schema(description = "会员类型", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
private String payMemberType;
|
private String payMemberType;
|
||||||
|
|
||||||
|
@Schema(description = "商户订单编号", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private String merchantOrderId;
|
||||||
}
|
}
|
@ -156,5 +156,12 @@ public class PaidMemberUserDO extends TenantBaseDO {
|
|||||||
*/
|
*/
|
||||||
private String payMemberType;
|
private String payMemberType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商户订单编号
|
||||||
|
*
|
||||||
|
* 例如说,内部系统 A 的订单号,需要保证每个 PayAppDO 唯一
|
||||||
|
*/
|
||||||
|
private String merchantOrderId;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
@ -8,6 +8,7 @@ import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
|
|||||||
import cn.iocoder.yudao.module.member.dal.dataobject.user.PaidMemberUserDO;
|
import cn.iocoder.yudao.module.member.dal.dataobject.user.PaidMemberUserDO;
|
||||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||||
|
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 会员 Service 接口
|
* 会员 Service 接口
|
||||||
@ -84,6 +85,12 @@ public interface PaidMemberUserService {
|
|||||||
*/
|
*/
|
||||||
PageResult<PaidMemberUserDO> getMemberUserPage(PaidMemberUserPageReqVO pageReqVO);
|
PageResult<PaidMemberUserDO> getMemberUserPage(PaidMemberUserPageReqVO pageReqVO);
|
||||||
|
|
||||||
PaidMemberUserDO getByIdAndPayOrderId(Long id,Long payOrderId);
|
PaidMemberUserDO getByPayOrderId(Long payOrderId);
|
||||||
|
|
||||||
|
/***
|
||||||
|
* 创建交易订单
|
||||||
|
*/
|
||||||
|
public Long buildTradeOrder(PaidMemberUserDO updateReqVO);
|
||||||
|
|
||||||
|
void updateTradeOrderId(Long merchantOrderId, Long payOrderId);
|
||||||
}
|
}
|
@ -1,11 +1,21 @@
|
|||||||
package cn.iocoder.yudao.module.member.service.user;
|
package cn.iocoder.yudao.module.member.service.user;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import cn.hutool.core.date.DatePattern;
|
import cn.hutool.core.date.DatePattern;
|
||||||
import cn.hutool.core.date.DateUtil;
|
import cn.hutool.core.date.DateUtil;
|
||||||
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
|
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
|
||||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||||
|
import cn.iocoder.yudao.module.member.api.address.MemberAddressApi;
|
||||||
|
import cn.iocoder.yudao.module.member.api.address.dto.MemberAddressRespDTO;
|
||||||
import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
|
import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
|
||||||
import cn.iocoder.yudao.module.member.service.membercardtype.PaidMemberCardTypeService;
|
import cn.iocoder.yudao.module.member.service.membercardtype.PaidMemberCardTypeService;
|
||||||
|
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
|
||||||
|
import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper;
|
||||||
|
import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
|
||||||
|
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderRefundStatusEnum;
|
||||||
|
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum;
|
||||||
|
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
|
||||||
|
import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
@ -25,7 +35,9 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
|||||||
import cn.iocoder.yudao.module.member.dal.mysql.user.PaidMemberUserMapper;
|
import cn.iocoder.yudao.module.member.dal.mysql.user.PaidMemberUserMapper;
|
||||||
|
|
||||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||||
|
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
|
||||||
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
||||||
|
import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getTerminal;
|
||||||
import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.*;
|
import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -43,6 +55,14 @@ public class PaidMemberUserServiceImpl implements PaidMemberUserService {
|
|||||||
@Resource
|
@Resource
|
||||||
private PaidMemberCardTypeService memberCardTypeService;
|
private PaidMemberCardTypeService memberCardTypeService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private TradeOrderUpdateService tradeOrderUpdateService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private TradeOrderMapper tradeOrderMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private MemberAddressApi addressApi;
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -186,11 +206,51 @@ public class PaidMemberUserServiceImpl implements PaidMemberUserService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PaidMemberUserDO getByIdAndPayOrderId(Long id, Long payOrderId) {
|
public PaidMemberUserDO getByPayOrderId(Long payOrderId) {
|
||||||
PaidMemberUserDO paidMemberUserDO = paidMemberUserMapper.selectOne(
|
PaidMemberUserDO paidMemberUserDO = paidMemberUserMapper.selectOne(
|
||||||
new LambdaQueryWrapperX<PaidMemberUserDO>().eqIfPresent(PaidMemberUserDO::getId,id)
|
new LambdaQueryWrapperX<PaidMemberUserDO>().eqIfPresent(PaidMemberUserDO::getPayOrderId,payOrderId));
|
||||||
.eqIfPresent(PaidMemberUserDO::getPayOrderId,payOrderId));
|
if(paidMemberUserDO == null){
|
||||||
|
throw exception(PAID_MEMBER_ORDER_NOT_EXISTS);
|
||||||
|
}
|
||||||
return paidMemberUserDO;
|
return paidMemberUserDO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long buildTradeOrder(PaidMemberUserDO updateReqVO) {
|
||||||
|
TradeOrderDO orderDO = new TradeOrderDO();
|
||||||
|
orderDO.setUserId(updateReqVO.getUid());
|
||||||
|
orderDO.setType(TradeOrderTypeEnum.NORMAL.getType());
|
||||||
|
orderDO.setNo(updateReqVO.getOrderNo());
|
||||||
|
orderDO.setStatus(TradeOrderStatusEnum.UNPAID.getStatus());
|
||||||
|
orderDO.setPayStatus(false);
|
||||||
|
orderDO.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus());
|
||||||
|
orderDO.setProductCount(1);
|
||||||
|
orderDO.setUserIp(getClientIP()).setTerminal(getTerminal());
|
||||||
|
orderDO.setDeliveryType(DeliveryTypeEnum.EXPRESS.getType());
|
||||||
|
//获取用户地址
|
||||||
|
// MemberAddressRespDTO address = addressApi.getDefaultAddress(updateReqVO.getUid());
|
||||||
|
// orderDO.setReceiverName(address.getName()).setReceiverMobile(address.getMobile())
|
||||||
|
// .setReceiverAreaId(address.getAreaId()).setReceiverDetailAddress(address.getDetailAddress());
|
||||||
|
//插入新增交易订单
|
||||||
|
tradeOrderMapper.insert(orderDO);
|
||||||
|
//查询最新的交易订单数据
|
||||||
|
List<TradeOrderDO> list = tradeOrderMapper.selectList(new LambdaQueryWrapperX<TradeOrderDO>()
|
||||||
|
.eqIfPresent(TradeOrderDO::getUserId,updateReqVO.getUid())
|
||||||
|
.orderByDesc(TradeOrderDO::getId));
|
||||||
|
if(list.isEmpty()){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
orderDO = CollUtil.getFirst(list);
|
||||||
|
return orderDO.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateTradeOrderId(Long merchantOrderId, Long payOrderId) {
|
||||||
|
TradeOrderDO orderDO = tradeOrderMapper.selectOne(new LambdaQueryWrapperX<TradeOrderDO>()
|
||||||
|
.eqIfPresent(TradeOrderDO::getId,merchantOrderId));
|
||||||
|
orderDO.setPayOrderId(payOrderId);
|
||||||
|
tradeOrderMapper.updateById(orderDO);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
@ -95,6 +95,7 @@ public class AppPaidMembeController {
|
|||||||
//初始化支付金额,单位为分
|
//初始化支付金额,单位为分
|
||||||
int price = 0;
|
int price = 0;
|
||||||
Long payOrderId = null;
|
Long payOrderId = null;
|
||||||
|
Long merchantOrderId = null;
|
||||||
if(reqVO.getCardName().equals("试用")){
|
if(reqVO.getCardName().equals("试用")){
|
||||||
BeanUtils.copyProperties(memberUserDO,paidMemberUserSaveReqVO);
|
BeanUtils.copyProperties(memberUserDO,paidMemberUserSaveReqVO);
|
||||||
paidMemberUserSaveReqVO.setPayMemberType(reqVO.getCardName());
|
paidMemberUserSaveReqVO.setPayMemberType(reqVO.getCardName());
|
||||||
@ -139,14 +140,19 @@ public class AppPaidMembeController {
|
|||||||
Long id = paidMemberUserService.createMemberUser(paidMemberUserSaveReqVO);
|
Long id = paidMemberUserService.createMemberUser(paidMemberUserSaveReqVO);
|
||||||
//查询新增的会员订单记录
|
//查询新增的会员订单记录
|
||||||
paidMemberUserDO = paidMemberUserService.getMemberUser(id);
|
paidMemberUserDO = paidMemberUserService.getMemberUser(id);
|
||||||
// 创建支付订单号
|
//创建交易订单号
|
||||||
|
merchantOrderId = paidMemberUserService.buildTradeOrder(paidMemberUserDO);
|
||||||
|
paidMemberUserDO.setMerchantOrderId(String.valueOf(merchantOrderId));
|
||||||
|
//创建支付订单号
|
||||||
payOrderId = payOrderService.createOrder(new PayOrderCreateReqDTO()
|
payOrderId = payOrderService.createOrder(new PayOrderCreateReqDTO()
|
||||||
.setAppId(1L).setUserIp(getClientIP())
|
.setAppId(1L).setUserIp(getClientIP())
|
||||||
.setMerchantOrderId(""+paidMemberUserDO.getId())
|
.setMerchantOrderId(""+paidMemberUserDO.getMerchantOrderId())
|
||||||
.setSubject(PAID_MEMBER_USER_SUBJECT).setBody("")
|
.setSubject(PAID_MEMBER_USER_SUBJECT).setBody("")
|
||||||
.setPrice(paidMemberUserDO.getPrice())
|
.setPrice(paidMemberUserDO.getPrice())
|
||||||
.setExpireTime(addTime(Duration.ofHours(2L)))); // TODO @芋艿:支付超时时间
|
.setExpireTime(addTime(Duration.ofHours(2L)))); // TODO @芋艿:支付超时时间
|
||||||
paidMemberUserDO.setPayOrderId(payOrderId);
|
paidMemberUserDO.setPayOrderId(payOrderId);
|
||||||
|
//将支付订单号更新到交易订单中
|
||||||
|
paidMemberUserService.updateTradeOrderId(merchantOrderId,payOrderId);
|
||||||
//跟新支付订单号进付费会员表里
|
//跟新支付订单号进付费会员表里
|
||||||
paidMemberUserService.update(paidMemberUserDO);
|
paidMemberUserService.update(paidMemberUserDO);
|
||||||
}
|
}
|
||||||
|
@ -154,17 +154,6 @@ public class PayOrderServiceImpl implements PayOrderService {
|
|||||||
// 1.32 校验支付渠道是否有效
|
// 1.32 校验支付渠道是否有效
|
||||||
PayChannelDO channel = validateChannelCanSubmit(order.getAppId(), reqVO.getChannelCode());
|
PayChannelDO channel = validateChannelCanSubmit(order.getAppId(), reqVO.getChannelCode());
|
||||||
PayClient client = channelService.getPayClient(channel.getId());
|
PayClient client = channelService.getPayClient(channel.getId());
|
||||||
// 如果订单扩展里面已经存在该订单号的订单扩展(正在等待),则关闭已有的订单扩展,然后再创建(解决先点击微信支付,然后再使用余额支付会产生两个扩展订单的bug)
|
|
||||||
LambdaQueryWrapperX<PayOrderExtensionDO> wrapperX = new LambdaQueryWrapperX<>();
|
|
||||||
wrapperX.eq(PayOrderExtensionDO::getOrderId, order.getId()).eq(PayOrderExtensionDO::getStatus, PayOrderStatusEnum.WAITING.getStatus());
|
|
||||||
List<PayOrderExtensionDO> existOrderExtensionList = orderExtensionMapper.selectList(wrapperX);
|
|
||||||
if (existOrderExtensionList != null){
|
|
||||||
// 考虑到这种重复的扩展订单一般就一个(最大值取决与支付方式个数),所以不使用批处理插入
|
|
||||||
for (PayOrderExtensionDO payOrderExtensionDO : existOrderExtensionList) {
|
|
||||||
payOrderExtensionDO.setStatus(PayOrderStatusEnum.CLOSED.getStatus());
|
|
||||||
orderExtensionMapper.updateById(payOrderExtensionDO);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 2. 插入 PayOrderExtensionDO
|
// 2. 插入 PayOrderExtensionDO
|
||||||
String no = noRedisDAO.generate(payProperties.getOrderNoPrefix());
|
String no = noRedisDAO.generate(payProperties.getOrderNoPrefix());
|
||||||
PayOrderExtensionDO orderExtension = PayOrderConvert.INSTANCE.convert(reqVO, userIp)
|
PayOrderExtensionDO orderExtension = PayOrderConvert.INSTANCE.convert(reqVO, userIp)
|
||||||
@ -277,12 +266,7 @@ public class PayOrderServiceImpl implements PayOrderService {
|
|||||||
PayChannelDO channel = channelService.validPayChannel(channelId);
|
PayChannelDO channel = channelService.validPayChannel(channelId);
|
||||||
// 更新支付订单为已支付
|
// 更新支付订单为已支付
|
||||||
TenantUtils.execute(channel.getTenantId(), () -> getSelf().notifyOrder(channel, notify));
|
TenantUtils.execute(channel.getTenantId(), () -> getSelf().notifyOrder(channel, notify));
|
||||||
// 获取订单扩展
|
|
||||||
PayOrderExtensionDO payOrderExtensionDO = orderExtensionMapper.selectByNo(notify.getOutTradeNo());
|
PayOrderExtensionDO payOrderExtensionDO = orderExtensionMapper.selectByNo(notify.getOutTradeNo());
|
||||||
// 判断订单是否更新为已支付
|
|
||||||
if (Objects.equals(PayOrderStatusEnum.SUCCESS.getStatus(), orderMapper.selectById(payOrderExtensionDO.getOrderId()).getStatus())){
|
|
||||||
|
|
||||||
// 充值判断
|
|
||||||
PayWalletRechargeDO walletRechargeDO = walletRechargeMapper.selectOne("pay_order_id", payOrderExtensionDO.getOrderId());
|
PayWalletRechargeDO walletRechargeDO = walletRechargeMapper.selectOne("pay_order_id", payOrderExtensionDO.getOrderId());
|
||||||
if (walletRechargeDO != null){
|
if (walletRechargeDO != null){
|
||||||
walletRechargeDO.setPayStatus(true);
|
walletRechargeDO.setPayStatus(true);
|
||||||
@ -291,7 +275,6 @@ public class PayOrderServiceImpl implements PayOrderService {
|
|||||||
payWalletService.addWalletBalance(walletRechargeDO.getWalletId(),walletRechargeDO.getPayOrderId().toString(),RECHARGE,walletRechargeDO.getTotalPrice());
|
payWalletService.addWalletBalance(walletRechargeDO.getWalletId(),walletRechargeDO.getPayOrderId().toString(),RECHARGE,walletRechargeDO.getTotalPrice());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通知并更新订单的支付结果
|
* 通知并更新订单的支付结果
|
||||||
|
Loading…
Reference in New Issue
Block a user