feat-im-0607-ch
ch 2 years ago
parent e593b5e7dd
commit 425b501204

@ -3,6 +3,6 @@ src/icons
public public
dist dist
node_modules node_modules
src/plugins/msb-im.js src/utils/msb-im.js
src/utils/proto-rsq.js src/utils/proto-rsq.js
src/utils/proto-rsp.js src/utils/proto-rsp.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'; import request from '@/utils/request.js';
export const login = (params) => { export const login = (params) => {
return request({ return request({
@ -33,3 +40,37 @@ export const searchSummary = (params) => {
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,
});
};

@ -2,19 +2,140 @@
* @Author: ch * @Author: ch
* @Date: 2022-06-07 15:41:05 * @Date: 2022-06-07 15:41:05
* @LastEditors: ch * @LastEditors: ch
* @LastEditTime: 2022-06-08 14:45:05 * @LastEditTime: 2022-06-09 10:25:49
* @Description: file content * @Description: file content
*/ */
import * as api from '@/api/chat';
import dayjs from 'dayjs';
const state = { const state = {
sessionData: [], 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 = { const mutations = {
SET_SESSION_DATA(state, data) { SET_SESSION_DATA(state, data) {
state.sessionData = 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 { export default {
state, state,
getters, getters,

@ -2,42 +2,51 @@
* @Author: ch * @Author: ch
* @Date: 2022-06-07 15:52:37 * @Date: 2022-06-07 15:52:37
* @LastEditors: ch * @LastEditors: ch
* @LastEditTime: 2022-06-08 14:51:11 * @LastEditTime: 2022-06-09 10:59:15
* @Description: file content * @Description: file content
*/ */
import * as api from '@/api/chat';
import config from '@/configs';
import $store from '@/store'; import $store from '@/store';
import MsbIm from '@/utils/msb-im'; import MsbIm from '@/utils/msb-im';
import { FormatJsonSearch, ToAsyncAwait } from '@/utils/utils'; import { FormatJsonSearch, ToAsyncAwait } from '@/utils/utils';
const Im = new MsbIm({ const Im = new MsbIm({
reconnect: true, reconnect: true,
}); });
const ImInit = async () => { const ImInit = () => {
// const { error, result } = await ApiGetCurrentUser(); return new Promise((reslove, reject) => {
// if (error) { const storeUc = $store.state.auth.userInfo;
// return false; if (!storeUc) {
// } ImInit();
// const { error: er, result: res } = await ApiGetSoketTicket(); return false;
// if (er) { }
// return false; api.getCustomeServiceTicket().then(async (res) => {
// } const par = FormatJsonSearch({
const par = FormatJsonSearch({ client: res.client,
// client: res.client, ticket: res.ticket,
// ticket: res.ticket, user: storeUc.userId,
client: 'yan_xuan', // nickname: storeUc.employeeName,
ticket: '9kpEgiLzVG14znSTvElLOJE5MEMa/EGdexhab4CbDmLzDGnE+UXmVOvUs4ixyPeQ', // avatar: storeUc.avatar,
// 1普通用户 2客服链接 // 1普通用户 2客服链接
connect: 2, connect: 2,
user: 2, // user: 2,
// nickname: result.nickname, // client: 'yan_xuan',
nickname: '周渺', // ticket: '9kpEgiLzVG14znSTvElLOJE5MEMa/EGdexhab4CbDmLzDGnE+UXmVOvUs4ixyPeQ',
// avatar: result.avatar, // nickname: '周渺',
avatar: 'https://msb-edu-dev.oss-cn-beijing.aliyuncs.com/uc/account-avatar/桌面水果.jpg', // 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 = () => { Im.interceptors.dataChangeAfter = () => {

@ -2,7 +2,7 @@
* @Author: ch * @Author: ch
* @Date: 2022-05-18 14:54:47 * @Date: 2022-05-18 14:54:47
* @LastEditors: ch * @LastEditors: ch
* @LastEditTime: 2022-06-08 15:10:31 * @LastEditTime: 2022-06-09 11:50:54
* @Description: file content * @Description: file content
*/ */
import '@/utils/poto-req'; import '@/utils/poto-req';
@ -37,7 +37,6 @@ class MsbIm {
defaultOption = { defaultOption = {
ioKey: 'traceId', ioKey: 'traceId',
reconnect: true, reconnect: true,
logout: false,
}; };
socket = null; socket = null;
isOpen = false; isOpen = false;
@ -45,7 +44,7 @@ class MsbIm {
interceptors = { interceptors = {
dataChangeBefore: null, dataChangeBefore: null,
dataChangeAfter: null, dataChangeAfter: null,
onLogout: null, onClose: null,
onMessage: null, onMessage: null,
}; };
sessionData = []; sessionData = [];
@ -70,11 +69,6 @@ class MsbIm {
}; };
const message = async (res) => { const message = async (res) => {
const result = fromatPotoRsp(res.data); const result = fromatPotoRsp(res.data);
// 没有401了要优化
if (result.code === 401) {
this.logout();
return false;
}
this.interceptors.onMessage && this.interceptors.onMessage(result); this.interceptors.onMessage && this.interceptors.onMessage(result);
// 处理服务端主动推送的消息 // 处理服务端主动推送的消息
this[onMessage](result); this[onMessage](result);
@ -89,14 +83,27 @@ class MsbIm {
}; };
const close = () => { const close = () => {
console.log('[im] close'); console.log('[im] close');
if (this.option.reconnect && !this.option.logout) { this.interceptors.onClose && this.interceptors.onClose();
this[connect]();
}
this.option.logout = false;
}; };
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 { try {
this.socket = new WebSocket(option.url); this.socket = new WebSocket(this.option.url);
this.socket.binaryType = 'arraybuffer'; this.socket.binaryType = 'arraybuffer';
this.socket.onopen = () => { this.socket.onopen = () => {
open(); open();
@ -110,22 +117,6 @@ class MsbIm {
} catch (e) { } catch (e) {
reject(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); const par = fromatPotoReq(data.traceId, data.traceType, data.content);
if (WebSocket) { if (uni) {
this.socket.send(par);
} else if (uni) {
this.socket.send({ this.socket.send({
data: par, data: par,
fail(e) { fail(e) {
reject({ error: e }); reject({ error: e });
}, },
}); });
} else if (WebSocket) {
this.socket.send(par);
} }
}); });
} }
@ -214,9 +205,11 @@ class MsbIm {
heart(); heart();
}, 1000); }, 1000);
}; };
this[connect]({ this.option = {
...this.option,
...config, ...config,
}) };
this[connect]()
.then((res) => { .then((res) => {
resolve(res); resolve(res);
heart(); 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); console.log('[im] 获取会话列表--start', par);
let { error, result } = await ToAsyncAwait(this[send](par)); let { error, result } = await ToAsyncAwait(this[send](par));
console.log('[im] 获取会话列表--end', result); console.log('[im] 获取会话列表--end', result, error);
if (error) { if (error) {
return Promise.reject(error); return Promise.reject(error);
} }
@ -303,7 +291,7 @@ class MsbIm {
}; };
console.log('[im] 获取会话历史消息--start', par); console.log('[im] 获取会话历史消息--start', par);
const { error, result } = await ToAsyncAwait(this[send](par)); const { error, result } = await ToAsyncAwait(this[send](par));
console.log('[im] 获取会话历史消息--end', result); console.log('[im] 获取会话历史消息--end', result, error);
if (error) { if (error) {
return Promise.reject(error); return Promise.reject(error);
} }
@ -330,7 +318,7 @@ class MsbIm {
}; };
console.log('[im] 会话已读--start', par); console.log('[im] 会话已读--start', par);
const { error, result } = await this[send](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) => { let newData = this.sessionData.map((item) => {
if (item.id == params.content.sessionId) { if (item.id == params.content.sessionId) {
@ -357,7 +345,6 @@ class MsbIm {
let msgCtx = { let msgCtx = {
...params.content, ...params.content,
...par, ...par,
fromId: params.fromId,
createTimeStamp: new Date().getTime(), createTimeStamp: new Date().getTime(),
sendStatus: 'loading', sendStatus: 'loading',
}; };
@ -368,7 +355,7 @@ class MsbIm {
console.log('[im] 发送消息--start', par); console.log('[im] 发送消息--start', par);
const { error, result } = await ToAsyncAwait(this[send](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--; ) { for (let i = curSession.messageList.length; i--; ) {
const item = curSession.messageList[i]; const item = curSession.messageList[i];
@ -402,7 +389,7 @@ class MsbIm {
content: params.content, content: params.content,
}) })
); );
console.log('[im] 重新发送消息--end', result); console.log('[im] 重新发送消息--end', result, error);
params.createTimeStamp = result.createTimeStamp; params.createTimeStamp = result.createTimeStamp;
if (error) { if (error) {
params.sendStatus = 'fail'; params.sendStatus = 'fail';
@ -426,12 +413,12 @@ class MsbIm {
async createSession(params) { async createSession(params) {
const par = { const par = {
traceId: CreateUUID(), traceId: CreateUUID(),
traceType: 21, traceType: 9,
...params, ...params,
}; };
console.log('[im] 主动创建会话--start', par); console.log('[im] 主动创建会话--start', par);
const { result, error } = await ToAsyncAwait(this[send](par)); const { result, error } = await ToAsyncAwait(this[send](par));
console.log('[im] 主动创建会话--start', result); console.log('[im] 主动创建会话--end', result, error);
if (error) { if (error) {
return Promise.reject(error); return Promise.reject(error);
} }

@ -37,12 +37,12 @@
<div class="row"> <div class="row">
<div class="session-name">{{ item.fromNickname }}</div> <div class="session-name">{{ item.fromNickname }}</div>
<div class="session-time"> <div class="session-time">
{{ store.getters['chat/parseTime'](item.lastMessage.createTimeStamp) }} {{ store.getters['im/parseTime'](item.lastMessage.createTimeStamp) }}
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="session-content"> <div class="session-content">
{{ store.getters['chat/parseText'](item.lastMessage) }} {{ store.getters['im/parseText'](item.lastMessage) }}
</div> </div>
</div> </div>
</div> </div>
@ -149,11 +149,10 @@
const { proxy } = getCurrentInstance(); const { proxy } = getCurrentInstance();
const router = useRouter(); const router = useRouter();
const store = useStore(); const store = useStore();
ImInit().then(() => { ImInit().then(() => {
Im.getSessionList(); Im.getSessionList();
}); });
// store.dispatch('chat/connect');
// store.dispatch('chat/querySession');
const opts = computed(() => store.state.chat.opts); const opts = computed(() => store.state.chat.opts);
// //
@ -172,7 +171,7 @@
message: '', message: '',
}); });
const sessionList = computed(() => store.state.im.sessionData); 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) => { const handleChangeSession = (id) => {
currentSessionId.value = id; currentSessionId.value = id;
// ID // ID
@ -186,17 +185,10 @@
if (!sessionMessageList.length) { if (!sessionMessageList.length) {
Im.getHistoryMsg(); 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(() => { const sessionMessageList = computed(() => {
console.log(currentSession.messageList?.length, 'currentSession');
return currentSession.messageList || []; return currentSession.messageList || [];
}); });
const refsMessageList = ref(null); const refsMessageList = ref(null);
@ -214,7 +206,6 @@
}); });
const handleLoadMore = () => { const handleLoadMore = () => {
Im.getHistoryMsg(); Im.getHistoryMsg();
// store.dispatch('chat/querySessionMessage', { topMessageId: unref(sessionMessageList)[0].id });
}; };
const handleSendMessage = (e) => { const handleSendMessage = (e) => {
if (e && e.shiftKey) { if (e && e.shiftKey) {
@ -222,14 +213,13 @@
} else { } else {
if (state.message) { if (state.message) {
Im.sendMsg({ Im.sendMsg({
fromId: 2, //userInfo.value.id, fromId: userInfo.value.userId,
content: { content: {
toSessionId: currentSessionId.value, toSessionId: currentSessionId.value,
payload: { text: state.message }, payload: { text: state.message },
type: 1, type: 1,
}, },
}); });
// store.dispatch('chat/submitMessage', { text: state.message });
state.message = ''; state.message = '';
} else { } else {
proxy.$message.warning('发送消息不能为空'); proxy.$message.warning('发送消息不能为空');
@ -249,12 +239,12 @@
columns: [ columns: [
{ {
label: '账号', label: '账号',
prop: 'account', prop: 'waiterId',
width: 160, width: 160,
}, },
{ {
label: '客户昵称', label: '客户昵称',
prop: 'nickname', prop: 'waiterNickname',
minWidth: 160, minWidth: 160,
}, },
{ {
@ -278,9 +268,9 @@
}, },
], ],
}); });
const customerServiceList = computed(() => store.state.chat.customerServiceList); const customerServiceList = computed(() => store.state.im.customerServiceList);
const handleTransferSession = () => { const handleTransferSession = () => {
store.dispatch('chat/queryCustomerService'); store.dispatch('im/queryCustomerService');
transferVisible.value = true; transferVisible.value = true;
}; };
const handleConfirmTransfer = async (row) => { const handleConfirmTransfer = async (row) => {
@ -289,8 +279,8 @@
confirmButtonText: '确定', confirmButtonText: '确定',
}); });
if (res.action === 'confirm') { if (res.action === 'confirm') {
store.dispatch('chat/submitTransferSession', { store.dispatch('im/submitTransferSession', {
toWaiterId: row.userId, toWaiterId: row.waiterId,
sessionId: unref(currentSessionId), sessionId: unref(currentSessionId),
reason: res.value, reason: res.value,
}); });

@ -73,26 +73,26 @@
<div v-else-if="messageType[props.message.type] === 'image'" class="content shadow"> <div v-else-if="messageType[props.message.type] === 'image'" class="content shadow">
<el-image <el-image
alt="[图片消息]" alt="[图片消息]"
:src="store.getters['chat/parseImage'](props.message.payload)" :src="store.getters['im/parseImage'](props.message.payload)"
style="max-width: 240px" style="max-width: 240px"
/> />
</div> </div>
<div v-else-if="messageType[props.message.type] === 'video'" class="shadow"> <div v-else-if="messageType[props.message.type] === 'video'" class="shadow">
<video controls width="240"> <video controls width="240">
<source :src="store.getters['chat/parseVideo'](props.message.payload)" type="video/mp4" /> <source :src="store.getters['im/parseVideo'](props.message.payload)" type="video/mp4" />
<object <object
:data="store.getters['chat/parseVideo'](props.message.payload)" :data="store.getters['im/parseVideo'](props.message.payload)"
height="240" height="240"
width="320" width="320"
></object> ></object>
</video> </video>
</div> </div>
<div v-else class="content shadow"> <div v-else class="content shadow">
{{ store.getters['chat/parseText'](props.message) }} {{ store.getters['im/parseText'](props.message) }}
</div> </div>
</div> </div>
<div v-if="!['revoke', 'notify'].includes(messageType[props.message.type])" class="time"> <div v-if="!['revoke', 'notify'].includes(messageType[props.message.type])" class="time">
{{ store.getters['chat/parseTime'](props.message.createTimeStamp) }} {{ store.getters['im/parseTime'](props.message.createTimeStamp) }}
</div> </div>
</div> </div>
</template> </template>
@ -111,7 +111,7 @@
}, },
}); });
const messageType = computed(() => store.state.chat.messageType); 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) => { const handleProduct = (id) => {
router.push({ router.push({
name: 'UpdateProduct', name: 'UpdateProduct',

@ -23,7 +23,7 @@ export default (configEnv) => {
// target: 'http://192.168.10.109:8090/', // 显雨 // target: 'http://192.168.10.109:8090/', // 显雨
// target: 'http://192.168.10.5:4500', // 高玉 // target: 'http://192.168.10.5:4500', // 高玉
// target: 'http://192.168.10.67:8090', // 罗战 // 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: 'http://192.168.10.124:8090', // 舒梦娇
target: 'https://k8s-horse-gateway.mashibing.cn/', // 测试地址 target: 'https://k8s-horse-gateway.mashibing.cn/', // 测试地址
// target: 'https://you-gateway.mashibing.com', // 生产环境 // target: 'https://you-gateway.mashibing.com', // 生产环境

Loading…
Cancel
Save