From 425b501204f05dfb3789279bd9af6eed3c32bc72 Mon Sep 17 00:00:00 2001 From: ch Date: Thu, 9 Jun 2022 11:52:09 +0800 Subject: [PATCH] sdk --- .eslintignore | 2 +- src/api/chat/index.js | 41 ++++++++++++ src/store/modules/im/im.js | 127 ++++++++++++++++++++++++++++++++++++- src/utils/im.js | 63 ++++++++++-------- src/utils/msb-im.js | 83 ++++++++++-------------- src/views/chat/index.vue | 32 ++++------ src/views/chat/message.vue | 12 ++-- vite.config.js | 2 +- 8 files changed, 255 insertions(+), 107 deletions(-) diff --git a/.eslintignore b/.eslintignore index f3bb79a..03c5c1d 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,6 +3,6 @@ src/icons public dist node_modules -src/plugins/msb-im.js +src/utils/msb-im.js src/utils/proto-rsq.js src/utils/proto-rsp.js \ No newline at end of file diff --git a/src/api/chat/index.js b/src/api/chat/index.js index c6bc2c0..ff501e6 100644 --- a/src/api/chat/index.js +++ b/src/api/chat/index.js @@ -1,3 +1,10 @@ +/* + * @Author: xwk + * @Date: 2022-05-24 17:00:26 + * @LastEditors: ch + * @LastEditTime: 2022-06-09 10:10:47 + * @Description: file content + */ import request from '@/utils/request.js'; export const login = (params) => { return request({ @@ -33,3 +40,37 @@ export const searchSummary = (params) => { params, }); }; +/** + * 获取链接凭证 + */ +export const getCustomeServiceTicket = () => { + return request({ + url: '/mall/im/admin/ticket', + method: 'get', + params: { + ticketType: 'CONNECT_TICKET', + }, + }); +}; +/** + * 获取可转移客服列表 + * @param {*} params + */ +export const customerServiceList = (params) => { + return request({ + url: '/mall/im/admin/waiter', + method: 'get', + params, + }); +}; +/** + * 转移客服 + * @param {*} params + */ +export const transferCustomerService = (data) => { + return request({ + url: '/mall/im/admin/waiter/transfer', + method: 'post', + data, + }); +}; diff --git a/src/store/modules/im/im.js b/src/store/modules/im/im.js index 56aebc1..10c64aa 100644 --- a/src/store/modules/im/im.js +++ b/src/store/modules/im/im.js @@ -2,19 +2,140 @@ * @Author: ch * @Date: 2022-06-07 15:41:05 * @LastEditors: ch - * @LastEditTime: 2022-06-08 14:45:05 + * @LastEditTime: 2022-06-09 10:25:49 * @Description: file content */ +import * as api from '@/api/chat'; +import dayjs from 'dayjs'; const state = { sessionData: [], + customerServiceList: [], + messageType: { 1: 'text', 2: 'audio', 3: 'image', 4: 'video', 5: 'revoke', 6: 'custom', 7: 'notify' }, +}; + +const getters = { + parseTime: () => { + return (timestamp) => { + dayjs(new Date(timestamp)).format('MM-DD HH:mm:ss'); + }; + }, + parseText: () => { + return ({ payload, type }) => { + if (type === 2) { + payload = '[语音]'; + } else if (type === 3) { + payload = '[图片]'; + } else if (type === 4) { + payload = '[视频]'; + } else if (type === 5) { + payload = '[撤回消息]'; + } else if (type === 6 || type === 1) { + try { + // payload = JSON.parse(payload.value); + if ('text' in payload) { + payload = payload.text; + } else if ('linkJump' in payload) { + payload = '[超链接]'; + } else if ('orderNo' in payload) { + payload = '[订单信息]'; + } else if ('productImageUrl' in payload) { + payload = '[商品信息]'; + } else { + payload = '[未知数据]'; + } + } catch (e) { + payload = '[解析异常]'; + } + } else if (type === 7) { + payload = '[撤回消息]'; + } else { + payload = '[未知类型]'; + } + return payload; + }; + }, + parseImage: () => { + return (payload) => { + try { + // payload = JSON.parse(payload); + if ('url' in payload) { + payload = payload.url; + } else { + payload = '[未知图片]'; + } + } catch (e) { + payload = '[解析异常]'; + } + return payload; + }; + }, + parseVideo: () => { + return (payload) => { + try { + // payload = JSON.parse(payload); + if ('url' in payload) { + payload = payload.url; + } else { + payload = '[未知视频]'; + } + } catch (e) { + payload = '[解析异常]'; + } + return payload; + }; + }, + parseContent: () => { + return (payload) => { + try { + // payload = JSON.parse(payload); + if ('linkJump' in payload) { + payload.type = 'link'; + } else if ('orderNo' in payload) { + payload.type = 'order'; + } else if ('productImageUrl' in payload) { + payload.type = 'product'; + } else { + payload = '[未知消息]'; + } + } catch (e) { + payload = '[解析异常]'; + } + return payload; + }; + }, }; -const getters = {}; const mutations = { SET_SESSION_DATA(state, data) { state.sessionData = data; }, + SET_SERVICE_LIST(state, data) { + state.customerServiceList = data; + }, +}; +const actions = { + /** + * 查询可转移客服列表 + */ + queryCustomerService: ({ commit }) => { + api.customerServiceList({ + length: 100, + pageIndex: 1, + }).then((res) => { + commit('SET_SERVICE_LIST', res.records); + }); + }, + /** + * 提交转移会话 + */ + submitTransferSession: ({ dispatch }, data) => { + api.transferCustomerService({ + storeId: 1, + ...data, + }).then((res) => { + console.log(res, 'resresres'); + }); + }, }; -const actions = {}; export default { state, getters, diff --git a/src/utils/im.js b/src/utils/im.js index 2bb4dfa..2ad55fa 100644 --- a/src/utils/im.js +++ b/src/utils/im.js @@ -2,42 +2,51 @@ * @Author: ch * @Date: 2022-06-07 15:52:37 * @LastEditors: ch - * @LastEditTime: 2022-06-08 14:51:11 + * @LastEditTime: 2022-06-09 10:59:15 * @Description: file content */ +import * as api from '@/api/chat'; +import config from '@/configs'; import $store from '@/store'; import MsbIm from '@/utils/msb-im'; import { FormatJsonSearch, ToAsyncAwait } from '@/utils/utils'; const Im = new MsbIm({ reconnect: true, }); -const ImInit = async () => { - // const { error, result } = await ApiGetCurrentUser(); - // if (error) { - // return false; - // } - // const { error: er, result: res } = await ApiGetSoketTicket(); - // if (er) { - // return false; - // } - const par = FormatJsonSearch({ - // client: res.client, - // ticket: res.ticket, - client: 'yan_xuan', - ticket: '9kpEgiLzVG14znSTvElLOJE5MEMa/EGdexhab4CbDmLzDGnE+UXmVOvUs4ixyPeQ', - // 1普通用户 2客服链接 - connect: 2, - user: 2, - // nickname: result.nickname, - nickname: '周渺', - // avatar: result.avatar, - avatar: 'https://msb-edu-dev.oss-cn-beijing.aliyuncs.com/uc/account-avatar/桌面水果.jpg', +const ImInit = () => { + return new Promise((reslove, reject) => { + const storeUc = $store.state.auth.userInfo; + if (!storeUc) { + ImInit(); + return false; + } + api.getCustomeServiceTicket().then(async (res) => { + const par = FormatJsonSearch({ + client: res.client, + ticket: res.ticket, + user: storeUc.userId, + // nickname: storeUc.employeeName, + // avatar: storeUc.avatar, + // 1普通用户 2客服链接 + connect: 2, + // user: 2, + // client: 'yan_xuan', + // ticket: '9kpEgiLzVG14znSTvElLOJE5MEMa/EGdexhab4CbDmLzDGnE+UXmVOvUs4ixyPeQ', + // nickname: '周渺', + // avatar: 'https://msb-edu-dev.oss-cn-beijing.aliyuncs.com/uc/account-avatar/桌面水果.jpg', + }); + const { error, result } = await ToAsyncAwait( + Im.init({ + url: `${config.socketURL}${par}`, + }) + ); + if (error) { + reject(error); + } else { + reslove(result); + } + }); }); - await ToAsyncAwait( - Im.init({ - url: `ws://192.168.10.94:8090/ws${par}`, - }) - ); }; Im.interceptors.dataChangeAfter = () => { diff --git a/src/utils/msb-im.js b/src/utils/msb-im.js index ab26cff..d743eba 100644 --- a/src/utils/msb-im.js +++ b/src/utils/msb-im.js @@ -2,7 +2,7 @@ * @Author: ch * @Date: 2022-05-18 14:54:47 * @LastEditors: ch - * @LastEditTime: 2022-06-08 15:10:31 + * @LastEditTime: 2022-06-09 11:50:54 * @Description: file content */ import '@/utils/poto-req'; @@ -37,7 +37,6 @@ class MsbIm { defaultOption = { ioKey: 'traceId', reconnect: true, - logout: false, }; socket = null; isOpen = false; @@ -45,7 +44,7 @@ class MsbIm { interceptors = { dataChangeBefore: null, dataChangeAfter: null, - onLogout: null, + onClose: null, onMessage: null, }; sessionData = []; @@ -70,11 +69,6 @@ class MsbIm { }; const message = async (res) => { const result = fromatPotoRsp(res.data); - // 没有401了要优化 - if (result.code === 401) { - this.logout(); - return false; - } this.interceptors.onMessage && this.interceptors.onMessage(result); // 处理服务端主动推送的消息 this[onMessage](result); @@ -89,14 +83,27 @@ class MsbIm { }; const close = () => { console.log('[im] close'); - if (this.option.reconnect && !this.option.logout) { - this[connect](); - } - this.option.logout = false; + this.interceptors.onClose && this.interceptors.onClose(); }; - if (WebSocket) { + if (uni) { + this.socket = uni.connectSocket({ + ...this.option, + fail(e) { + reject(e); + }, + }); + this.socket.onOpen(() => { + open(); + this.socket.onMessage((res) => { + message(res); + }); + }); + this.socket.onClose(() => { + close(); + }); + } else if (WebSocket) { try { - this.socket = new WebSocket(option.url); + this.socket = new WebSocket(this.option.url); this.socket.binaryType = 'arraybuffer'; this.socket.onopen = () => { open(); @@ -110,22 +117,6 @@ class MsbIm { } catch (e) { reject(e); } - } else if (uni) { - this.socket = uni.connectSocket({ - ...option, - fail(e) { - reject(e); - }, - }); - this.socket.onOpen(() => { - onOpen(); - this.socket.onMessage((res) => { - onMessage(res); - }); - }); - this.socket.onClose(() => { - onClose(); - }); } }); } @@ -147,15 +138,15 @@ class MsbIm { }; const par = fromatPotoReq(data.traceId, data.traceType, data.content); - if (WebSocket) { - this.socket.send(par); - } else if (uni) { + if (uni) { this.socket.send({ data: par, fail(e) { reject({ error: e }); }, }); + } else if (WebSocket) { + this.socket.send(par); } }); } @@ -214,9 +205,11 @@ class MsbIm { heart(); }, 1000); }; - this[connect]({ + this.option = { + ...this.option, ...config, - }) + }; + this[connect]() .then((res) => { resolve(res); heart(); @@ -226,11 +219,6 @@ class MsbIm { }); }); } - logout() { - this.option.logout = true; - this.socket.close(); - this.interceptors.onLogout && this.interceptors.onLogout(); - } /** * 设置数据 */ @@ -261,7 +249,7 @@ class MsbIm { console.log('[im] 获取会话列表--start', par); let { error, result } = await ToAsyncAwait(this[send](par)); - console.log('[im] 获取会话列表--end', result); + console.log('[im] 获取会话列表--end', result, error); if (error) { return Promise.reject(error); } @@ -303,7 +291,7 @@ class MsbIm { }; console.log('[im] 获取会话历史消息--start', par); const { error, result } = await ToAsyncAwait(this[send](par)); - console.log('[im] 获取会话历史消息--end', result); + console.log('[im] 获取会话历史消息--end', result, error); if (error) { return Promise.reject(error); } @@ -330,7 +318,7 @@ class MsbIm { }; console.log('[im] 会话已读--start', par); const { error, result } = await this[send](par); - console.log('[im] 会话已读--end', result); + console.log('[im] 会话已读--end', result, error); let newData = this.sessionData.map((item) => { if (item.id == params.content.sessionId) { @@ -357,7 +345,6 @@ class MsbIm { let msgCtx = { ...params.content, ...par, - fromId: params.fromId, createTimeStamp: new Date().getTime(), sendStatus: 'loading', }; @@ -368,7 +355,7 @@ class MsbIm { console.log('[im] 发送消息--start', par); const { error, result } = await ToAsyncAwait(this[send](par)); - console.log('[im] 发送消息--end', result); + console.log('[im] 发送消息--end', result, error); // 接到通知,标记消息是否发送成功 for (let i = curSession.messageList.length; i--; ) { const item = curSession.messageList[i]; @@ -402,7 +389,7 @@ class MsbIm { content: params.content, }) ); - console.log('[im] 重新发送消息--end', result); + console.log('[im] 重新发送消息--end', result, error); params.createTimeStamp = result.createTimeStamp; if (error) { params.sendStatus = 'fail'; @@ -426,12 +413,12 @@ class MsbIm { async createSession(params) { const par = { traceId: CreateUUID(), - traceType: 21, + traceType: 9, ...params, }; console.log('[im] 主动创建会话--start', par); const { result, error } = await ToAsyncAwait(this[send](par)); - console.log('[im] 主动创建会话--start', result); + console.log('[im] 主动创建会话--end', result, error); if (error) { return Promise.reject(error); } diff --git a/src/views/chat/index.vue b/src/views/chat/index.vue index 206b51d..01a6c3f 100644 --- a/src/views/chat/index.vue +++ b/src/views/chat/index.vue @@ -37,12 +37,12 @@
{{ item.fromNickname }}
- {{ store.getters['chat/parseTime'](item.lastMessage.createTimeStamp) }} + {{ store.getters['im/parseTime'](item.lastMessage.createTimeStamp) }}
- {{ store.getters['chat/parseText'](item.lastMessage) }} + {{ store.getters['im/parseText'](item.lastMessage) }}
@@ -149,11 +149,10 @@ const { proxy } = getCurrentInstance(); const router = useRouter(); const store = useStore(); + ImInit().then(() => { Im.getSessionList(); }); - // store.dispatch('chat/connect'); - // store.dispatch('chat/querySession'); const opts = computed(() => store.state.chat.opts); // 统计 @@ -172,7 +171,7 @@ message: '', }); const sessionList = computed(() => store.state.im.sessionData); - const currentSession = computed(() => sessionList.value.find((item) => item.id === currentSessionId.value) || []); + const currentSession = computed(() => sessionList.value.find((item) => item.id === currentSessionId.value)); const handleChangeSession = (id) => { currentSessionId.value = id; // 设置实例对象当前会话ID @@ -186,17 +185,10 @@ if (!sessionMessageList.length) { Im.getHistoryMsg(); } - // store.dispatch('chat/revoke', 28); - // store.dispatch('chat/revoke', 31); - // store.commit('chat/setCurrentSession', id); - // store.commit('chat/setMessageList', []); - // store.dispatch('chat/querySessionMessage'); - // store.dispatch('chat/submitRead'); }; // 聊天 const sessionMessageList = computed(() => { - console.log(currentSession.messageList?.length, 'currentSession'); return currentSession.messageList || []; }); const refsMessageList = ref(null); @@ -214,7 +206,6 @@ }); const handleLoadMore = () => { Im.getHistoryMsg(); - // store.dispatch('chat/querySessionMessage', { topMessageId: unref(sessionMessageList)[0].id }); }; const handleSendMessage = (e) => { if (e && e.shiftKey) { @@ -222,14 +213,13 @@ } else { if (state.message) { Im.sendMsg({ - fromId: 2, //userInfo.value.id, + fromId: userInfo.value.userId, content: { toSessionId: currentSessionId.value, payload: { text: state.message }, type: 1, }, }); - // store.dispatch('chat/submitMessage', { text: state.message }); state.message = ''; } else { proxy.$message.warning('发送消息不能为空'); @@ -249,12 +239,12 @@ columns: [ { label: '账号', - prop: 'account', + prop: 'waiterId', width: 160, }, { label: '客户昵称', - prop: 'nickname', + prop: 'waiterNickname', minWidth: 160, }, { @@ -278,9 +268,9 @@ }, ], }); - const customerServiceList = computed(() => store.state.chat.customerServiceList); + const customerServiceList = computed(() => store.state.im.customerServiceList); const handleTransferSession = () => { - store.dispatch('chat/queryCustomerService'); + store.dispatch('im/queryCustomerService'); transferVisible.value = true; }; const handleConfirmTransfer = async (row) => { @@ -289,8 +279,8 @@ confirmButtonText: '确定', }); if (res.action === 'confirm') { - store.dispatch('chat/submitTransferSession', { - toWaiterId: row.userId, + store.dispatch('im/submitTransferSession', { + toWaiterId: row.waiterId, sessionId: unref(currentSessionId), reason: res.value, }); diff --git a/src/views/chat/message.vue b/src/views/chat/message.vue index f3de091..56e80d3 100644 --- a/src/views/chat/message.vue +++ b/src/views/chat/message.vue @@ -73,26 +73,26 @@
- {{ store.getters['chat/parseText'](props.message) }} + {{ store.getters['im/parseText'](props.message) }}
- {{ store.getters['chat/parseTime'](props.message.createTimeStamp) }} + {{ store.getters['im/parseTime'](props.message.createTimeStamp) }}
@@ -111,7 +111,7 @@ }, }); const messageType = computed(() => store.state.chat.messageType); - const content = computed(() => store.getters['chat/parseContent'](props.message.payload)); + const content = computed(() => store.getters['im/parseContent'](props.message.payload)); const handleProduct = (id) => { router.push({ name: 'UpdateProduct', diff --git a/vite.config.js b/vite.config.js index a7aec9e..b0d0191 100644 --- a/vite.config.js +++ b/vite.config.js @@ -23,7 +23,7 @@ export default (configEnv) => { // target: 'http://192.168.10.109:8090/', // 显雨 // target: 'http://192.168.10.5:4500', // 高玉 // target: 'http://192.168.10.67:8090', // 罗战 - // target: 'http://192.168.10.93:8090', // 周渺 + // target: 'http://192.168.10.94:8090', // 周渺 // target: 'http://192.168.10.124:8090', // 舒梦娇 target: 'https://k8s-horse-gateway.mashibing.cn/', // 测试地址 // target: 'https://you-gateway.mashibing.com', // 生产环境