向文可 3 years ago
commit 1014350a42

@ -2,3 +2,4 @@ VITE_BASE_URL=/api
VITE_SOCKET_URL=wss://k8s-horse-gateway.mashibing.cn/ws VITE_SOCKET_URL=wss://k8s-horse-gateway.mashibing.cn/ws
#VITE_SOCKET_URL=ws://192.168.10.93:8090/ws #VITE_SOCKET_URL=ws://192.168.10.93:8090/ws
VITE_REQUEST_TIMEOUT=5000 VITE_REQUEST_TIMEOUT=5000
VITE_BROWSER_URL = https://k8s-shop-pc.mashibing.cn

@ -1,3 +1,4 @@
VITE_BASE_URL=https://you-gateway.mashibing.com VITE_BASE_URL=https://you-gateway.mashibing.com
VITE_SOCKET_URL=wss://you-gateway.mashibing.com/ws VITE_SOCKET_URL=wss://you-gateway.mashibing.com/ws
VITE_REQUEST_TIMEOUT=20000 VITE_REQUEST_TIMEOUT=20000
VITE_BROWSER_URL = https://k8s-shop-pc.mashibing.cn

@ -1,3 +1,4 @@
VITE_BASE_URL=https://k8s-horse-gateway.mashibing.cn/ VITE_BASE_URL=https://k8s-horse-gateway.mashibing.cn/
VITE_SOCKET_URL=wss://k8s-horse-gateway.mashibing.cn/ws VITE_SOCKET_URL=wss://k8s-horse-gateway.mashibing.cn/ws
VITE_REQUEST_TIMEOUT=20000 VITE_REQUEST_TIMEOUT=20000
VITE_BROWSER_URL = https://k8s-shop-pc.mashibing.cn

@ -4,5 +4,5 @@ public
dist dist
node_modules node_modules
src/utils/msb-im.js src/utils/msb-im.js
src/utils/proto-rsq.js src/utils/poto-req.js
src/utils/proto-rsp.js src/utils/proto-rsp.js

@ -2,7 +2,7 @@
* @Author: xwk * @Author: xwk
* @Date: 2022-05-24 17:00:26 * @Date: 2022-05-24 17:00:26
* @LastEditors: ch * @LastEditors: ch
* @LastEditTime: 2022-06-09 10:10:47 * @LastEditTime: 2022-06-13 14:26:53
* @Description: file content * @Description: file content
*/ */
import request from '@/utils/request.js'; import request from '@/utils/request.js';
@ -52,6 +52,16 @@ export const getCustomeServiceTicket = () => {
}, },
}); });
}; };
/**
* 获取当前客服
*/
export const getCustomerService = () => {
return request({
url: '/mall/im/admin/waiterUser/getWaiterByUserId',
method: 'get',
});
};
/** /**
* 获取可转移客服列表 * 获取可转移客服列表
* @param {*} params * @param {*} params

@ -0,0 +1,363 @@
import { login } from '@/api/chat';
import config from '@/configs';
import { ElMessage } from '@/plugins/element-plus';
import { UUID } from '@/utils/chat';
import dayjs from 'dayjs';
const state = () => ({
socket: null,
heart: null,
queue: [],
task: [],
currentSession: null,
messageList: [],
messageType: { 1: 'text', 2: 'audio', 3: 'image', 4: 'video', 5: 'revoke', 6: 'custom', 7: 'notify' },
curCustomerService: {},
sessionData: [],
customerServiceList: [],
opts: {
customerServiceType: [
{
label: '售前',
value: 1,
},
{
label: '售后',
value: 2,
},
{
label: '发货',
value: 3,
},
],
},
});
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);
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 mutations = {
setSocket: (state, data) => (state.socket = data),
setHeart: (state, data) => (state.heart = data),
setTask: (state, data) => (state.task = data),
addTask: (state, data) => state.task.push(data),
delTask: (state, data) => state.task.splice(data, 1),
setCurrentSession: (state, data) => (state.currentSession = data),
setSessionData: (state, data) => (state.sessionData = data),
setMessageList: (state, data) => (state.messageList = data),
setCustomerServiceList: (state, data) => (state.customerServiceList = data),
SET_CUR_SERVICE(state, data) {
state.curCustomerService = data || {};
},
SET_SESSION_DATA(state, data) {
state.sessionData = data;
},
SET_SERVICE_LIST(state, data) {
state.customerServiceList = data;
},
};
const actions = {
/**
* 创建连接
*/
connect: async ({ state, commit, dispatch }) => {
let { ticket } = await login({ storeId: 1 });
return new Promise((resolve, reject) => {
if (window.WebSocket) {
const socket = new WebSocket(`${config.socketURL}?client=${ticket}&type=2`);
socket.onmessage = ({ data }) => {
dispatch('receive', data);
};
socket.onopen = () => {
commit(
'setHeart',
setInterval(() => {
dispatch('heart');
}, 3000)
);
console.info('[chat] open');
resolve(socket);
};
socket.onclose = () => {
clearInterval(state.heart);
console.info('[chat] close');
};
socket.onerror = (e) => {
clearInterval(state.heart);
console.info('[chat] error', e);
reject(e);
};
commit('setSocket', socket);
} else {
ElMessage.error('当前浏览器不支持 WebSocket');
reject('not support websocket');
}
});
},
/**
* 发送心跳任务监测
*/
heart: ({ state, dispatch }) => {
dispatch('send', {
traceType: 26,
content: { storeId: 1 },
});
// console.info('[chat] heart');
state.task.forEach((item) => {
dispatch('send', item);
});
},
/**
* 执行任务
*/
invoke: ({ commit, dispatch }, data) => {
data.traceId = UUID();
commit('addTask', data);
dispatch('send', data);
},
/**
* 撤销任务
*/
revoke: ({ state, commit }, data) => {
console.info(state.task.length);
commit(
'setTask',
state.task.filter((item) => item.traceType !== data)
);
console.info(state.task.length);
},
/**
* 发送数据
*/
send: ({ state }, data) => {
if (window.WebSocket) {
if (state.socket?.readyState === WebSocket.OPEN) {
data.traceId = data.traceId || UUID();
state.socket.send(JSON.stringify(data));
if (data.traceType !== 26) {
console.info('[chat] send', data);
}
}
}
},
/**
* 接收数据
*/
receive: ({ state, commit, dispatch }, data) => {
data = JSON.parse(data);
if (data.traceType !== 0) {
let index = state.task.findIndex((item) => item.traceId === data.traceId);
if (index !== -1) {
console.info('[chat] data', data);
commit('delTask', index);
dispatch('handle', data);
} else if (data.traceType === 25) {
console.info('[chat] msg', data);
dispatch('handle', data);
} else {
console.info('[chat] deprecated', data);
}
}
},
/**
* 处理数据
*/
handle: ({ state, commit, dispatch }, { code, traceType, content }) => {
if (code === 200) {
switch (traceType) {
case 25: // 收到消息
if (content.sessionId === state.currentSession) {
commit('setMessageList', [...state.messageList, content]);
dispatch('submitRead');
} else {
dispatch('querySession');
}
break;
case 32: // 发送消息
commit('setMessageList', [...state.messageList, content]);
break;
case 27: // 会话列表
commit('setSessionData', content);
break;
case 28: // 消息列表
commit('setMessageList', [...content, ...state.messageList]);
break;
case 29: // 客服列表
commit('setCustomerServiceList', content);
break;
case 31: // 已读消息
dispatch('querySession');
break;
default:
break;
}
}
},
/**
* 查询会话列表
*/
querySession: ({ dispatch }) => {
dispatch('invoke', {
traceType: 27,
content: { storeId: 1 },
});
},
/**
* 查询会话消息列表
*/
querySessionMessage: ({ state, dispatch }, data) => {
dispatch('invoke', {
traceType: 28,
content: { sessionId: state.currentSession, size: 10, topMessageId: null, ...data },
});
},
/**
* 查询可转移客服列表
*/
queryCustomerService: ({ dispatch }) => {
dispatch('invoke', {
traceType: 29,
content: { storeId: 1 },
});
},
/**
* 提交转移会话
*/
submitTransferSession: ({ dispatch }, data) => {
dispatch('invoke', {
traceType: 30,
content: { storeId: 1, ...data },
});
},
/**
* 提交已读消息
*/
submitRead: ({ state, dispatch }) => {
dispatch('invoke', {
traceType: 31,
content: { sessionId: state.currentSession },
});
},
/**
* 提交发送消息
*/
submitMessage: ({ state, dispatch }, payload) => {
dispatch('invoke', {
traceType: 32,
content: { payload, toSessionId: state.currentSession, type: 1 },
});
},
/**
* 提交发送图片
*/
submitImage: ({ state, dispatch }, payload) => {
dispatch('invoke', {
traceType: 32,
content: { payload, toSessionId: state.currentSession, type: 3 },
});
},
/**
* 提交发送视频
*/
submitVideo: ({ state, dispatch }, payload) => {
dispatch('invoke', {
traceType: 32,
content: { payload, toSessionId: state.currentSession, type: 4 },
});
},
};
export default {
state,
getters,
mutations,
actions,
};

@ -1,18 +1,18 @@
import { login } from '@/api/chat'; /*
import config from '@/configs'; * @Author: ch
import { ElMessage } from '@/plugins/element-plus'; * @Date: 2022-06-07 15:41:05
import { UUID } from '@/utils/chat'; * @LastEditors: ch
* @LastEditTime: 2022-06-13 17:16:27
* @Description: file content
*/
import * as api from '@/api/chat';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
const state = () => ({ const state = {
socket: null, curCustomerService: {},
heart: null, sessionData: [],
queue: [],
task: [],
sessionData: {},
currentSession: null,
messageList: [],
messageType: { 1: 'text', 2: 'audio', 3: 'image', 4: 'video', 5: 'revoke', 6: 'custom', 7: 'notify' },
customerServiceList: [], customerServiceList: [],
messageType: { 1: 'text', 2: 'audio', 3: 'image', 4: 'video', 5: 'revoke', 6: 'custom', 7: 'notify' },
opts: { opts: {
customerServiceType: [ customerServiceType: [
{ {
@ -29,7 +29,8 @@ const state = () => ({
}, },
], ],
}, },
}); };
const getters = { const getters = {
parseTime: () => { parseTime: () => {
return (timestamp) => { return (timestamp) => {
@ -48,7 +49,7 @@ const getters = {
payload = '[撤回消息]'; payload = '[撤回消息]';
} else if (type === 6 || type === 1) { } else if (type === 6 || type === 1) {
try { try {
payload = JSON.parse(payload); // payload = JSON.parse(payload.value);
if ('text' in payload) { if ('text' in payload) {
payload = payload.text; payload = payload.text;
} else if ('linkJump' in payload) { } else if ('linkJump' in payload) {
@ -64,7 +65,7 @@ const getters = {
payload = '[解析异常]'; payload = '[解析异常]';
} }
} else if (type === 7) { } else if (type === 7) {
payload = '[撤回消息]'; payload = payload.text;
} else { } else {
payload = '[未知类型]'; payload = '[未知类型]';
} }
@ -74,7 +75,7 @@ const getters = {
parseImage: () => { parseImage: () => {
return (payload) => { return (payload) => {
try { try {
payload = JSON.parse(payload); // payload = JSON.parse(payload);
if ('url' in payload) { if ('url' in payload) {
payload = payload.url; payload = payload.url;
} else { } else {
@ -89,7 +90,7 @@ const getters = {
parseVideo: () => { parseVideo: () => {
return (payload) => { return (payload) => {
try { try {
payload = JSON.parse(payload); // payload = JSON.parse(payload);
if ('url' in payload) { if ('url' in payload) {
payload = payload.url; payload = payload.url;
} else { } else {
@ -104,7 +105,7 @@ const getters = {
parseContent: () => { parseContent: () => {
return (payload) => { return (payload) => {
try { try {
payload = JSON.parse(payload); // payload = JSON.parse(payload);
if ('linkJump' in payload) { if ('linkJump' in payload) {
payload.type = 'link'; payload.type = 'link';
} else if ('orderNo' in payload) { } else if ('orderNo' in payload) {
@ -122,224 +123,46 @@ const getters = {
}, },
}; };
const mutations = { const mutations = {
setSocket: (state, data) => (state.socket = data), SET_CUR_SERVICE(state, data) {
setHeart: (state, data) => (state.heart = data), state.curCustomerService = data || {};
setTask: (state, data) => (state.task = data),
addTask: (state, data) => state.task.push(data),
delTask: (state, data) => state.task.splice(data, 1),
setCurrentSession: (state, data) => (state.currentSession = data),
setSessionData: (state, data) => (state.sessionData = data),
setMessageList: (state, data) => (state.messageList = data),
setCustomerServiceList: (state, data) => (state.customerServiceList = data),
};
const actions = {
/**
* 创建连接
*/
connect: async ({ state, commit, dispatch }) => {
let { ticket } = await login({ storeId: 1 });
return new Promise((resolve, reject) => {
if (window.WebSocket) {
const socket = new WebSocket(`${config.socketURL}?client=${ticket}&type=2`);
socket.onmessage = ({ data }) => {
dispatch('receive', data);
};
socket.onopen = () => {
commit(
'setHeart',
setInterval(() => {
dispatch('heart');
}, 3000)
);
console.info('[chat] open');
resolve(socket);
};
socket.onclose = () => {
clearInterval(state.heart);
console.info('[chat] close');
};
socket.onerror = (e) => {
clearInterval(state.heart);
console.info('[chat] error', e);
reject(e);
};
commit('setSocket', socket);
} else {
ElMessage.error('当前浏览器不支持 WebSocket');
reject('not support websocket');
}
});
}, },
/** SET_SESSION_DATA(state, data) {
* 发送心跳任务监测 state.sessionData = data;
*/
heart: ({ state, dispatch }) => {
dispatch('send', {
traceType: 26,
content: { storeId: 1 },
});
// console.info('[chat] heart');
state.task.forEach((item) => {
dispatch('send', item);
});
}, },
/** SET_SERVICE_LIST(state, data) {
* 执行任务 state.customerServiceList = data;
*/
invoke: ({ commit, dispatch }, data) => {
data.traceId = UUID();
commit('addTask', data);
dispatch('send', data);
},
/**
* 撤销任务
*/
revoke: ({ state, commit }, data) => {
console.info(state.task.length);
commit(
'setTask',
state.task.filter((item) => item.traceType !== data)
);
console.info(state.task.length);
}, },
/** };
* 发送数据 const actions = {
*/ queryCurCustomerService: ({ commit }) => {
send: ({ state }, data) => { api.getCustomerService().then((res) => {
if (window.WebSocket) { commit('SET_CUR_SERVICE', res);
if (state.socket?.readyState === WebSocket.OPEN) {
data.traceId = data.traceId || UUID();
state.socket.send(JSON.stringify(data));
if (data.traceType !== 26) {
console.info('[chat] send', data);
}
}
}
},
/**
* 接收数据
*/
receive: ({ state, commit, dispatch }, data) => {
data = JSON.parse(data);
if (data.traceType !== 0) {
let index = state.task.findIndex((item) => item.traceId === data.traceId);
if (index !== -1) {
console.info('[chat] data', data);
commit('delTask', index);
dispatch('handle', data);
} else if (data.traceType === 25) {
console.info('[chat] msg', data);
dispatch('handle', data);
} else {
console.info('[chat] deprecated', data);
}
}
},
/**
* 处理数据
*/
handle: ({ state, commit, dispatch }, { code, traceType, content }) => {
if (code === 200) {
switch (traceType) {
case 25: // 收到消息
if (content.sessionId === state.currentSession) {
commit('setMessageList', [...state.messageList, content]);
dispatch('submitRead');
} else {
dispatch('querySession');
}
break;
case 32: // 发送消息
commit('setMessageList', [...state.messageList, content]);
break;
case 27: // 会话列表
commit('setSessionData', content);
break;
case 28: // 消息列表
commit('setMessageList', [...content, ...state.messageList]);
break;
case 29: // 客服列表
commit('setCustomerServiceList', content);
break;
case 31: // 已读消息
dispatch('querySession');
break;
default:
break;
}
}
},
/**
* 查询会话列表
*/
querySession: ({ dispatch }) => {
dispatch('invoke', {
traceType: 27,
content: { storeId: 1 },
});
},
/**
* 查询会话消息列表
*/
querySessionMessage: ({ state, dispatch }, data) => {
dispatch('invoke', {
traceType: 28,
content: { sessionId: state.currentSession, size: 10, topMessageId: null, ...data },
}); });
}, },
/** /**
* 查询可转移客服列表 * 查询可转移客服列表
*/ */
queryCustomerService: ({ dispatch }) => { queryCustomerService: ({ commit }) => {
dispatch('invoke', { api.customerServiceList({
traceType: 29, length: 100,
content: { storeId: 1 }, pageIndex: 1,
}).then((res) => {
commit('SET_SERVICE_LIST', res.records);
}); });
}, },
/** /**
* 提交转移会话 * 提交转移会话
*/ */
submitTransferSession: ({ dispatch }, data) => { submitTransferSession: ({}, data) => {
dispatch('invoke', { return api
traceType: 30, .transferCustomerService({
content: { storeId: 1, ...data }, storeId: 1,
}); ...data,
}, })
/** .then((res) => {
* 提交已读消息 console.log(res, 'resresres');
*/ });
submitRead: ({ state, dispatch }) => {
dispatch('invoke', {
traceType: 31,
content: { sessionId: state.currentSession },
});
},
/**
* 提交发送消息
*/
submitMessage: ({ state, dispatch }, payload) => {
dispatch('invoke', {
traceType: 32,
content: { payload, toSessionId: state.currentSession, type: 1 },
});
},
/**
* 提交发送图片
*/
submitImage: ({ state, dispatch }, payload) => {
dispatch('invoke', {
traceType: 32,
content: { payload, toSessionId: state.currentSession, type: 3 },
});
},
/**
* 提交发送视频
*/
submitVideo: ({ state, dispatch }, payload) => {
dispatch('invoke', {
traceType: 32,
content: { payload, toSessionId: state.currentSession, type: 4 },
});
}, },
}; };
export default { export default {

@ -2,7 +2,7 @@
* @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-09 10:59:15 * @LastEditTime: 2022-06-13 17:32:19
* @Description: file content * @Description: file content
*/ */
import * as api from '@/api/chat'; import * as api from '@/api/chat';
@ -13,27 +13,15 @@ import { FormatJsonSearch, ToAsyncAwait } from '@/utils/utils';
const Im = new MsbIm({ const Im = new MsbIm({
reconnect: true, reconnect: true,
}); });
const ImInit = () => { const ImInit = (waiterId) => {
return new Promise((reslove, reject) => { return new Promise((reslove, reject) => {
const storeUc = $store.state.auth.userInfo;
if (!storeUc) {
ImInit();
return false;
}
api.getCustomeServiceTicket().then(async (res) => { api.getCustomeServiceTicket().then(async (res) => {
console.log(res);
const par = FormatJsonSearch({ const par = FormatJsonSearch({
client: res.client, client: res.client,
ticket: res.ticket, ticket: res.ticket,
user: storeUc.userId, user: waiterId,
// nickname: storeUc.employeeName,
// avatar: storeUc.avatar,
// 1普通用户 2客服链接
connect: 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( const { error, result } = await ToAsyncAwait(
Im.init({ Im.init({
@ -50,7 +38,7 @@ const ImInit = () => {
}; };
Im.interceptors.dataChangeAfter = () => { Im.interceptors.dataChangeAfter = () => {
$store.commit('im/SET_SESSION_DATA', JSON.parse(JSON.stringify(Im.sessionData))); $store.commit('chat/SET_SESSION_DATA', JSON.parse(JSON.stringify(Im.sessionData)));
// let msgCount = 0; // let msgCount = 0;
// Im.sessionData.forEach((i) => { // Im.sessionData.forEach((i) => {
// msgCount += i.unreadCount; // msgCount += i.unreadCount;
@ -61,7 +49,7 @@ Im.interceptors.dataChangeAfter = () => {
Im.interceptors.onLogout = () => { Im.interceptors.onLogout = () => {
Im.setSessionData([]); Im.setSessionData([]);
// Im.setCurSessionId(null); // Im.setCurSessionId(null);
$store.commit('im/SET_SESSION_DATA', []); $store.commit('chat/SET_SESSION_DATA', []);
// $store.commit('SET_IM_MSG_COUNT', 0); // $store.commit('SET_IM_MSG_COUNT', 0);
}; };
export { Im, ImInit }; export { Im, ImInit };

@ -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-10 19:15:46 * @LastEditTime: 2022-06-13 22:14:45
* @Description: file content * @Description: file content
*/ */
import '@/utils/poto-req'; import '@/utils/poto-req';
@ -34,6 +34,7 @@ const fromatPotoReq = (traceId, traceType, content) => {
message: res.getMessage(), message: res.getMessage(),
}; };
}; };
class MsbIm { class MsbIm {
defaultOption = { defaultOption = {
ioKey: 'traceId', ioKey: 'traceId',
@ -186,6 +187,12 @@ class MsbIm {
// 不在当前会话窗口则向会话消息加1条未读 // 不在当前会话窗口则向会话消息加1条未读
if (ctx.sessionId !== this.curSessionId) { if (ctx.sessionId !== this.curSessionId) {
curHisData.unreadCount++; curHisData.unreadCount++;
} else {
this.setRead({
content: {
sessionId: this.curSessionId,
},
});
} }
newData = historyData; newData = historyData;
} else { } else {
@ -193,9 +200,9 @@ class MsbIm {
newData = [ newData = [
...historyData, ...historyData,
{ {
fromAvatar: ctx.fromAvatar, fromAvatar: ctx.session.fromAvatar,
fromId: ctx.fromId, fromId: ctx.fromId,
fromNickname: ctx.fromNickname, fromNickname: ctx.session.fromNickname,
id: ctx.sessionId, id: ctx.sessionId,
lastMessage: ctx, lastMessage: ctx,
messageList: [ctx], messageList: [ctx],
@ -270,23 +277,24 @@ class MsbIm {
return Promise.reject(error); return Promise.reject(error);
} }
const { content } = result; const { content } = result;
// let newData = [];
content.sessionVOS.forEach((item) => { content.sessionVOS.forEach((item) => {
if (item.lastMessage) { if (item.lastMessage) {
item.lastMessage.payload = JSON.parse(item.lastMessage.payload || {}); item.lastMessage.payload = JSON.parse(item.lastMessage.payload || {});
} }
let historyData = this.sessionData; // let historyData = this.sessionData;
let hisIndex = historyData.findIndex((i) => i.id === item.id); // let hisIndex = historyData.findIndex((i) => i.id === item.id);
if (hisIndex >= 0) { // if (hisIndex >= 0) {
historyData[hisIndex].lastMessage = item.lastMessage; // historyData[hisIndex].lastMessage = item.lastMessage;
historyData[hisIndex].unreadCount++; // historyData[hisIndex].unreadCount++;
this.setSessionData(historyData); // newData.push(historyData[hisIndex]);
} else { // } else {
item.messageList = []; // item.messageList = [];
const newData = [...historyData, item]; // newData = [...newData, item];
this.setSessionData(newData); // }
}
}); });
this.setSessionData(content.sessionVOS);
return Promise.resolve(result); return Promise.resolve(result);
} }
/** /**
@ -369,6 +377,8 @@ class MsbIm {
} }
// 点发送,立即把消息加入消息列表,标记为发送中状态 // 点发送,立即把消息加入消息列表,标记为发送中状态
curSession.messageList.push(msgCtx); curSession.messageList.push(msgCtx);
this.setSessionData(this.sessionData);
// 超过时间未返回视为发送失败 // 超过时间未返回视为发送失败
this.timerStatus(msgCtx); this.timerStatus(msgCtx);
@ -455,5 +465,11 @@ class MsbIm {
} }
return Promise.resolve(result); return Promise.resolve(result);
} }
close() {
this.socket.close();
this.socket = null;
this.isOpen = false;
this.setSessionData([]);
}
} }
export default MsbIm; export default MsbIm;

@ -37,19 +37,19 @@
<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['im/parseTime'](item.lastMessage.createTimeStamp) }} {{ store.getters['chat/parseTime'](item.lastMessage.createTimeStamp) }}
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="session-content"> <div class="session-content">
{{ store.getters['im/parseText'](item.lastMessage) }} {{ store.getters['chat/parseText'](item.lastMessage) }}
</div> </div>
</div> </div>
</div> </div>
</li> </li>
</el-scrollbar> </el-scrollbar>
</div> </div>
<div v-if="currentSession" class="content"> <div v-if="currentSession.id" class="content">
<div class="content-header"> <div class="content-header">
<div class="content-header-left"> <div class="content-header-left">
<div class="name" :class="{ [`sex-` + currentSession?.fromSex]: true }"> <div class="name" :class="{ [`sex-` + currentSession?.fromSex]: true }">
@ -68,16 +68,11 @@
</div> </div>
</div> </div>
<el-scrollbar ref="refsMessageList" class="message-list"> <el-scrollbar ref="refsMessageList" class="message-list">
<el-button <el-button v-if="sessionMessageList.length" class="load" type="text" @click="handleLoadMore">
v-if="currentSession.messageList?.length"
class="load"
type="text"
@click="handleLoadMore"
>
加载更多 加载更多
</el-button> </el-button>
<message-item <message-item
v-for="(item, index) in currentSession.messageList" v-for="(item, index) in sessionMessageList"
:key="index" :key="index"
:message="item" :message="item"
:session="currentSession" :session="currentSession"
@ -149,10 +144,22 @@
const { proxy } = getCurrentInstance(); const { proxy } = getCurrentInstance();
const router = useRouter(); const router = useRouter();
const store = useStore(); const store = useStore();
store.dispatch('chat/queryCurCustomerService');
ImInit().then(() => { const socketInit = () => {
Im.getSessionList(); if (!store.state.chat.curCustomerService.waiterId) {
setTimeout(() => {
socketInit();
}, 1000);
return false;
}
ImInit(store.state.chat.curCustomerService.waiterId).then(() => {
Im.getSessionList();
});
};
onMounted(() => {
socketInit();
}); });
const opts = computed(() => store.state.chat.opts); const opts = computed(() => store.state.chat.opts);
// //
@ -170,26 +177,30 @@
const state = reactive({ const state = reactive({
message: '', message: '',
}); });
const sessionList = computed(() => store.state.im.sessionData); const sessionList = computed(() => store.state.chat.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
Im.setCurSessionId(id); Im.setCurSessionId(id);
// //
Im.setRead({ Im.setRead({
content: { content: {
sessionId: id, sessionId: id,
}, },
}); });
if (!sessionMessageList.length) { setTimeout(() => {
Im.getHistoryMsg(); if (!sessionMessageList.value.length) {
} Im.getHistoryMsg();
}
}, 100);
}; };
// //
const sessionMessageList = computed(() => { const sessionMessageList = ref([]);
return currentSession.messageList || []; watch(currentSession, () => {
sessionMessageList.value = currentSession.value.messageList || [];
}); });
const refsMessageList = ref(null); const refsMessageList = ref(null);
watch(sessionMessageList, (value, old) => { watch(sessionMessageList, (value, old) => {
@ -197,23 +208,26 @@
? refsMessageList.value.resize$.scrollHeight - refsMessageList.value.resize$.scrollTop ? refsMessageList.value.resize$.scrollHeight - refsMessageList.value.resize$.scrollTop
: 0; : 0;
nextTick(() => { nextTick(() => {
if (!old?.length || value.indexOf(old[0]) === 0) { // if (!old?.length || value.indexOf(old[0]) === 0) {
refsMessageList.value.setScrollTop(refsMessageList.value.resize$.scrollHeight); refsMessageList.value && refsMessageList.value.setScrollTop(refsMessageList.value.resize$.scrollHeight);
} else { // } else {
refsMessageList.value.setScrollTop(refsMessageList.value.resize$.scrollHeight - offset); // refsMessageList.value.setScrollTop(refsMessageList.value.resize$.scrollHeight - offset);
} // }
}); });
}); });
const handleLoadMore = () => { const handleLoadMore = () => {
Im.getHistoryMsg(); Im.getHistoryMsg();
}; };
const handleSendMessage = (e) => { const handleSendMessage = (e) => {
const curService = store.state.chat.curCustomerService;
if (e && e.shiftKey) { if (e && e.shiftKey) {
state.message += '\n'; state.message += '\n';
} else { } else {
if (state.message) { if (state.message) {
Im.sendMsg({ Im.sendMsg({
fromId: userInfo.value.userId, fromId: curService.waiterId,
fromAvatar: curService.waiterAvatar,
fromNickname: curService.waiterNickname,
content: { content: {
toSessionId: currentSessionId.value, toSessionId: currentSessionId.value,
payload: { text: state.message }, payload: { text: state.message },
@ -247,14 +261,14 @@
prop: 'waiterNickname', prop: 'waiterNickname',
minWidth: 160, minWidth: 160,
}, },
{ // {
label: '类型', // label: '',
prop: 'type', // prop: 'type',
width: 160, // width: 160,
slots: { // slots: {
default: ({ row }) => proxy.$dict(unref(opts).customerServiceType, row.type), // default: ({ row }) => proxy.$dict(unref(opts).customerServiceType, row.type),
}, // },
}, // },
{ {
label: '操作', label: '操作',
width: 100, width: 100,
@ -268,9 +282,9 @@
}, },
], ],
}); });
const customerServiceList = computed(() => store.state.im.customerServiceList); const customerServiceList = computed(() => store.state.chat.customerServiceList);
const handleTransferSession = () => { const handleTransferSession = () => {
store.dispatch('im/queryCustomerService'); store.dispatch('chat/queryCustomerService');
transferVisible.value = true; transferVisible.value = true;
}; };
const handleConfirmTransfer = async (row) => { const handleConfirmTransfer = async (row) => {
@ -279,11 +293,17 @@
confirmButtonText: '确定', confirmButtonText: '确定',
}); });
if (res.action === 'confirm') { if (res.action === 'confirm') {
store.dispatch('im/submitTransferSession', { store
toWaiterId: row.waiterId, .dispatch('chat/submitTransferSession', {
sessionId: unref(currentSessionId), toWaiterId: row.waiterId,
reason: res.value, sessionId: unref(currentSessionId),
}); reason: res.value,
})
.then((res) => {
currentSessionId.value = null;
Im.setCurSessionId(null);
Im.getSessionList();
});
transferVisible.value = false; transferVisible.value = false;
} }
} catch (e) { } catch (e) {
@ -306,12 +326,17 @@
}; };
const handleSendImage = async (e) => { const handleSendImage = async (e) => {
const file = e.target.files[0]; const file = e.target.files[0];
if (!file.type.includes('image')) {
proxy.$message.warning('只能发送图片哦~');
return false;
}
e.target.value = null; e.target.value = null;
let url = await upload('mall-product', 'im/', file); let url = await upload('mall-product', 'im/', file);
// store.dispatch('chat/submitImage', { url }); const curService = store.state.chat.curCustomerService;
Im.sendMsg({ Im.sendMsg({
fromId: 2, //userInfo.value.id, fromId: curService.waiterId,
fromAvatar: curService.waiterAvatar,
fromNickname: curService.waiterNickname,
content: { content: {
toSessionId: currentSessionId.value, toSessionId: currentSessionId.value,
payload: { url }, payload: { url },
@ -327,12 +352,19 @@
}; };
const handleSendVideo = async (e) => { const handleSendVideo = async (e) => {
const file = e.target.files[0]; const file = e.target.files[0];
if (!file.type.includes('video')) {
proxy.$message.warning('只能发送视频哦~');
return false;
}
e.target.value = null; e.target.value = null;
let url = await upload('mall-product', 'im/', file); let url = await upload('mall-product', 'im/', file);
// store.dispatch('chat/submitVideo', { url }); // store.dispatch('chat/submitVideo', { url });
const curService = store.state.chat.curCustomerService;
Im.sendMsg({ Im.sendMsg({
fromId: 2, //userInfo.value.id, fromId: curService.waiterId,
fromAvatar: curService.waiterAvatar,
fromNickname: curService.waiterNickname,
content: { content: {
toSessionId: currentSessionId.value, toSessionId: currentSessionId.value,
payload: { url }, payload: { url },
@ -362,7 +394,6 @@
display: flex; display: flex;
border: 1px solid #ebeef5; border: 1px solid #ebeef5;
.aside { .aside {
width: 240px;
border-right: 1px solid #ebeef5; border-right: 1px solid #ebeef5;
.aside-header { .aside-header {
height: 60px; height: 60px;

@ -25,7 +25,7 @@
<el-button type="text" @click="$copy(content.id)"></el-button> <el-button type="text" @click="$copy(content.id)"></el-button>
</div> </div>
</template> </template>
<div class="flex"> <div class="flex product" @click="handleProductDetail(content.id)">
<el-image :alt="content.name" height="64px" :src="content.productImageUrl" width="64px" /> <el-image :alt="content.name" height="64px" :src="content.productImageUrl" width="64px" />
<div class="right"> <div class="right">
<div class="name">{{ content.name }}</div> <div class="name">{{ content.name }}</div>
@ -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['im/parseImage'](props.message.payload)" :src="store.getters['chat/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['im/parseVideo'](props.message.payload)" type="video/mp4" /> <source :src="store.getters['chat/parseVideo'](props.message.payload)" type="video/mp4" />
<object <object
:data="store.getters['im/parseVideo'](props.message.payload)" :data="store.getters['chat/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['im/parseText'](props.message) }} {{ store.getters['chat/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['im/parseTime'](props.message.createTimeStamp) }} {{ store.getters['chat/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['im/parseContent']({ ...props.message.payload })); const content = computed(() => store.getters['chat/parseContent']({ ...props.message.payload }));
const handleProduct = (id) => { const handleProduct = (id) => {
router.push({ router.push({
name: 'UpdateProduct', name: 'UpdateProduct',
@ -121,6 +121,9 @@
}, },
}); });
}; };
const handleProductDetail = (id) => {
window.open(`${import.meta.env.VITE_BROWSER_URL}/goods/detail/${id}`, '_blank');
};
const handleOrder = (id) => { const handleOrder = (id) => {
router.push({ router.push({
name: 'OrderDetail', name: 'OrderDetail',
@ -148,6 +151,9 @@
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
} }
.product {
cursor: pointer;
}
.avatar { .avatar {
margin: 0 @layout-space; margin: 0 @layout-space;
} }

Loading…
Cancel
Save