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

@ -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

@ -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,
});
};

@ -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,

@ -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 = () => {

@ -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);
}

@ -37,12 +37,12 @@
<div class="row">
<div class="session-name">{{ item.fromNickname }}</div>
<div class="session-time">
{{ store.getters['chat/parseTime'](item.lastMessage.createTimeStamp) }}
{{ store.getters['im/parseTime'](item.lastMessage.createTimeStamp) }}
</div>
</div>
<div class="row">
<div class="session-content">
{{ store.getters['chat/parseText'](item.lastMessage) }}
{{ store.getters['im/parseText'](item.lastMessage) }}
</div>
</div>
</div>
@ -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,
});

@ -73,26 +73,26 @@
<div v-else-if="messageType[props.message.type] === 'image'" class="content shadow">
<el-image
alt="[图片消息]"
:src="store.getters['chat/parseImage'](props.message.payload)"
:src="store.getters['im/parseImage'](props.message.payload)"
style="max-width: 240px"
/>
</div>
<div v-else-if="messageType[props.message.type] === 'video'" class="shadow">
<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
:data="store.getters['chat/parseVideo'](props.message.payload)"
:data="store.getters['im/parseVideo'](props.message.payload)"
height="240"
width="320"
></object>
</video>
</div>
<div v-else class="content shadow">
{{ store.getters['chat/parseText'](props.message) }}
{{ store.getters['im/parseText'](props.message) }}
</div>
</div>
<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>
</template>
@ -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',

@ -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', // 生产环境

Loading…
Cancel
Save