fea: im改造

feature/im-0611-ch
ch 2 years ago
parent 30cdc50558
commit d04791081c

@ -64,6 +64,7 @@ import { mapState } from "vuex";
import { Message } from "element-ui"; import { Message } from "element-ui";
import { ApiGetCode, ApiPostLogin } from "@/plugins/api/account"; import { ApiGetCode, ApiPostLogin } from "@/plugins/api/account";
import { IsPhone } from "/plugins/utils"; import { IsPhone } from "/plugins/utils";
import {Im, ImInit} from '@/plugins/chat'
const COUNT_DOWN_TIME = 60; // const COUNT_DOWN_TIME = 60; //
export default { export default {
@ -167,7 +168,12 @@ export default {
this.dialogVisible = false; this.dialogVisible = false;
this.$store.commit("setToken", result.token); this.$store.commit("setToken", result.token);
this.$store.dispatch("getUserInfo"); this.$store.dispatch("getUserInfo");
this.$startWebSockets() // this.$startWebSockets()
// IM
ImInit().then(() => {
//
Im.getSessionList();
});
} }
} }
}); });

@ -42,7 +42,7 @@
</div> </div>
<div class="products-wrap-info__bottom flex flex-between"> <div class="products-wrap-info__bottom flex flex-between">
<span class="wrap-info-bottom__skuname">{{ <span class="wrap-info-bottom__skuname">{{
item.productSku.name item.productSku ? item.productSku.name : '--'
}}</span> }}</span>
<span class="wrap-info-bottom__count">{{ item.number }}</span> <span class="wrap-info-bottom__count">{{ item.number }}</span>
</div> </div>
@ -75,7 +75,7 @@
</div> </div>
<div class="products-wrap-info__bottom flex flex-between"> <div class="products-wrap-info__bottom flex flex-between">
<span class="wrap-info-bottom__skuname">{{ <span class="wrap-info-bottom__skuname">{{
item.productSku.name item.productSku ? item.productSku.name : '--'
}}</span> }}</span>
<span class="wrap-info-bottom__count">{{ item.number }}</span> <span class="wrap-info-bottom__count">{{ item.number }}</span>
</div> </div>

@ -96,6 +96,7 @@
</template> </template>
<script> <script>
import { mapState } from "vuex"; import { mapState } from "vuex";
import {Im, ImInit} from '@/plugins/chat'
const MENU_VALUE = { const MENU_VALUE = {
PERSONAL: 1, PERSONAL: 1,
ADDRESS: 2, ADDRESS: 2,
@ -134,9 +135,23 @@ export default {
}, },
}, },
mounted() { mounted() {
this.$startWebSockets();
this.initSocket();
}, },
methods: { methods: {
initSocket(){
if(!this.userInfo.id){
setTimeout(()=>{
this.initSocket();
},1000)
return false;
}
// IM
ImInit().then((res) => {
//
Im.getSessionList();
}).catch(e => {});
},
onLoginClick() { onLoginClick() {
this.$isLoginValidate(); this.$isLoginValidate();
}, },
@ -150,6 +165,7 @@ export default {
break; break;
case MENU_VALUE.LOGON_OUT: case MENU_VALUE.LOGON_OUT:
this.$store.dispatch("logout"); this.$store.dispatch("logout");
Im.close();
} }
}, },

@ -2,7 +2,7 @@
* @Author: ch * @Author: ch
* @Date: 2022-05-03 22:14:16 * @Date: 2022-05-03 22:14:16
* @LastEditors: ch * @LastEditors: ch
* @LastEditTime: 2022-05-31 18:58:47 * @LastEditTime: 2022-06-12 14:31:52
* @Description: file content * @Description: file content
*/ */
export default { export default {
@ -43,11 +43,12 @@ export default {
plugins: [ plugins: [
'@/plugins/element-ui', '@/plugins/element-ui',
'@/plugins/axios', '@/plugins/axios',
'@plugins/axiosTk.js', '@/plugins/axiosTk.js',
'@plugins/vue-inject.js', '@plugins/vue-inject.js',
'@/plugins/v-distpicker', '@/plugins/v-distpicker',
'@/plugins/router', '@/plugins/router',
'@/plugins/im' '@/plugins/im',
'@/plugins/chat'
], ],
// Auto import components: https://go.nuxtjs.dev/config-components // Auto import components: https://go.nuxtjs.dev/config-components

@ -2,7 +2,7 @@
* @Author: ch * @Author: ch
* @Date: 2022-05-04 17:48:12 * @Date: 2022-05-04 17:48:12
* @LastEditors: ch * @LastEditors: ch
* @LastEditTime: 2022-05-27 11:09:52 * @LastEditTime: 2022-06-13 10:50:28
* @Description: file content * @Description: file content
--> -->
@ -49,24 +49,34 @@
</template> </template>
<script> <script>
import UiEmpty from "@/components/UiEmpty.vue"; import UiEmpty from "@/components/UiEmpty.vue";
import {FormatDate, CreatUuid} from '@/plugins/utils' import {FormatDate, CreateUUID} from '@/plugins/utils';
import {Im, ImInit} from '@/plugins/chat'
export default { export default {
components: { UiEmpty }, components: { UiEmpty },
data() { data() {
return {}; return {};
}, },
watch:{
curSession(nv,ov){
if(nv.id != ov.id){
Im.getHistoryMsg();
}
}
},
computed: { computed: {
curSession(){
return this.$store.state.socketMsgData;
},
msgData(){ msgData(){
return [...this.$store.state.socketMsgData].map(i => { let data = this.curSession.messageList || [];
return data.map(i => {
let item = {...i} let item = {...i}
item.payload = JSON.parse(i.payload);
item.createTimeStamp = FormatDate(i.createTimeStamp, 'mm-dd hh:ii:ss') item.createTimeStamp = FormatDate(i.createTimeStamp, 'mm-dd hh:ii:ss')
return item; return item;
}).reverse(); }).reverse();
}, },
}, },
mounted() { mounted() {
// console.log(`socketMsgData`, this.$store.state);
setTimeout(()=>{ setTimeout(()=>{
this.readMsg(); this.readMsg();
},5000) },5000)
@ -77,15 +87,11 @@ export default {
* 把当前会话消息置为已读 * 把当前会话消息置为已读
*/ */
readMsg(){ readMsg(){
this.Socket.send(JSON.stringify({ Im.setRead({
traceId : CreatUuid(), content: {
traceType : "6", sessionId : this.curSession.id
content: { }
sessionId : this.msgData[0].sessionId });
}
}));
this.$store.commit("setUnreadCount", 0);
}, },
/** /**

@ -2,7 +2,7 @@
* @Author: ch * @Author: ch
* @Date: 2022-05-03 22:14:16 * @Date: 2022-05-03 22:14:16
* @LastEditors: ch * @LastEditors: ch
* @LastEditTime: 2022-06-10 10:26:51 * @LastEditTime: 2022-06-13 10:28:21
* @Description: file content * @Description: file content
--> -->
<template> <template>
@ -136,7 +136,6 @@ export default {
let {result:bannerList} = await ApiGetAdList({ let {result:bannerList} = await ApiGetAdList({
location : AD_LOCATION.HOME_BANNER location : AD_LOCATION.HOME_BANNER
}); });
console.log(bannerList);
// //
window.addEventListener("scroll", this.scrollEventMethod); window.addEventListener("scroll", this.scrollEventMethod);
}, },

@ -0,0 +1,20 @@
/*
* @Author: ch
* @Date: 2022-06-12 14:06:01
* @LastEditors: ch
* @LastEditTime: 2022-06-13 09:52:55
* @Description: file content
*/
import {axiosTk} from "../axiosTk";
import { ToAsyncAwait } from "../utils";
const BASE_URL = '/mall/im';
/**
* 获取soket登录秘钥
*/
export const ApiGetSoketTicket = () => ToAsyncAwait(axiosTk.get(`${BASE_URL}/ticket`, {
params: {
ticketType: 'CONNECT_TICKET'
}
}));

@ -4,12 +4,12 @@
* @Author: ch * @Author: ch
* @Date: 2022-05-03 23:04:45 * @Date: 2022-05-03 23:04:45
* @LastEditors: ch * @LastEditors: ch
* @LastEditTime: 2022-06-10 14:26:30 * @LastEditTime: 2022-06-12 14:35:43
* @Description: file content * @Description: file content
*/ */
let axios = null; let axios = null;
import { UUID_KEY } from "@/constants"; import { UUID_KEY } from "@/constants";
import { CreatUuid } from "@/plugins/utils"; import { CreateUUID } from "@/plugins/utils";
export default function ({ app, $axios, store, req }) { export default function ({ app, $axios, store, req }) {
let uuid = store.state.uuid; let uuid = store.state.uuid;
if (!uuid && req.headers.cookie) { if (!uuid && req.headers.cookie) {
@ -17,7 +17,7 @@ export default function ({ app, $axios, store, req }) {
uuid = uuid && uuid.replace(`${UUID_KEY}=`,'') uuid = uuid && uuid.replace(`${UUID_KEY}=`,'')
} }
if (!uuid) { if (!uuid) {
uuid = CreatUuid(16, 2); uuid = CreateUUID(16, 2);
store.commit('setUUID', uuid); store.commit('setUUID', uuid);
} }

@ -5,7 +5,7 @@
* @LastEditTime: 2022-06-10 14:26:57 * @LastEditTime: 2022-06-10 14:26:57
* @Description: file content * @Description: file content
*/ */
import { CreatUuid } from "@/plugins/utils"; import { CreateUUID } from "@/plugins/utils";
import { UUID_KEY } from "@/constants"; import { UUID_KEY } from "@/constants";
let axiosTk = null; let axiosTk = null;
export default function ({$axios, store, route, req}, inject) { export default function ({$axios, store, route, req}, inject) {
@ -16,7 +16,7 @@ export default function ({$axios, store, route, req}, inject) {
uuid = uuid && uuid.replace(`${UUID_KEY}=`,'') uuid = uuid && uuid.replace(`${UUID_KEY}=`,'')
} }
if (!uuid) { if (!uuid) {
uuid = CreatUuid(16, 2); uuid = CreateUUID(16, 2);
store.commit('setUUID', uuid); store.commit('setUUID', uuid);
} }

@ -0,0 +1,59 @@
/*
* @Author: ch
* @Date: 2022-06-12 14:04:56
* @LastEditors: ch
* @LastEditTime: 2022-06-13 10:52:48
* @Description: file content
*/
import MsbIm from '@/plugins/msbIm' ;
import { ToAsyncAwait, FormatJsonSearch } from './utils';
import { ApiGetSoketTicket } from '@/plugins/api/im';
import ENV from '@/plugins/config/env';
const Im = new MsbIm({
reconnect: true,
});
let ImInit = null;
export default ({store }) => {
ImInit = async () => {
const { error, result } = await ApiGetSoketTicket();
if (error) {
return false;
}
const par = FormatJsonSearch({
client: result.client,
ticket: result.ticket,
// 1普通用户 2客服链接
connect: 1,
user: store.state.userInfo.id,
nickname: store.state.userInfo.nickname,
avatar : store.state.userInfo.avatar
})
return await ToAsyncAwait(Im.init({
url: `${ENV.imUrl}/ws${par}`
}))
};
Im.interceptors.dataChangeAfter = () => {
let data = Im.sessionData.find(i => {
return (i.type === 4 && (typeof i.payload === 'string' ? JSON.parse(i.payload).type === 'system' : false));
}) || {}
let msgCount = data.unreadCount || 0;
Im.setCurSessionId(data.id);
store.commit('setSocketMsgData', JSON.parse(JSON.stringify(data)));
store.commit('setUnreadCount', msgCount);
}
Im.interceptors.onClose = () => {
Im.setSessionData([]);
Im.setCurSessionId(null);
store.commit('setSocketMsgData', {});
store.commit('setUnreadCount', 0);
}
}
export {
Im,
ImInit
}

@ -0,0 +1,464 @@
/*
* @Author: ch
* @Date: 2022-05-18 14:54:47
* @LastEditors: ch
* @LastEditTime: 2022-06-13 10:51:59
* @Description: file content
*/
import { CreateUUID, FormatDate, ToAsyncAwait } from "@/plugins/utils";
import './potoReq';
import './protoRsp';
const connect = Symbol('connect');
const send = Symbol('send');
const onMessage = Symbol('onMessage');
const fromatPotoReq = (traceId, traceType, content) => {
let messageModel = new proto.ReqModel();
messageModel.setTraceid(traceId);
messageModel.setTracetype(traceType);
content && messageModel.setContent(JSON.stringify(content));
return messageModel.serializeBinary();
},
fromatPotoRsp = (data) => {
const res = proto.RspModel.deserializeBinary(new Uint8Array(data));
let ctx = res.getContent();
ctx = ctx ? JSON.parse(ctx) : {};
if (ctx.payload) {
ctx.payload = JSON.parse(ctx.payload);
}
return {
content: ctx,
traceId: res.getTraceid(),
traceType: res.getTracetype(),
code: res.getCode(),
message: res.getMessage(),
};
};
class MsbIm {
defaultOption = {
ioKey: 'traceId',
reconnect: true,
};
socket = null;
isOpen = false;
queue = {};
interceptors = {
dataChangeBefore: null,
dataChangeAfter: null,
onClose: null,
onMessage: null,
};
sessionData = [];
curSessionId = null;
constructor(option) {
this.option = {
...this.defaultOption,
...option,
};
}
/**
* 创建连接返回一个Promise 创建成功并成功打开连接算连接成功
* @param {*} option
*/
[connect](option) {
return new Promise((resolve, reject) => {
const open = () => {
console.log('[im] open');
this.isOpen = true;
resolve(this.socket);
};
const message = async (res) => {
const result = fromatPotoRsp(res.data);
this.interceptors.onMessage && this.interceptors.onMessage(result);
// 处理服务端主动推送的消息
this[onMessage](result);
// 如果再消息堆里有此消息回调,则执行回调,并删除
const cbk = this.queue[result[this.option.ioKey]];
if (cbk) {
cbk(result.code !== 200 ? { error: result } : { result: result });
delete this.queue[result[this.option.ioKey]];
}
};
const close = () => {
console.log('[im] close');
this.interceptors.onClose && this.interceptors.onClose();
};
let isUni = false;
try {
isUni = uni;
} catch (e) {
isUni = false;
}
if (isUni) {
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(this.option.url);
this.socket.binaryType = 'arraybuffer';
this.socket.onopen = () => {
open();
};
this.socket.onmessage = (res) => {
message(res);
};
this.socket.onclose = () => {
close();
};
} catch (e) {
reject(e);
}
}
});
}
/**
* 向服务端发送消息||请求返回一个Promise对象收到ioKey对应的消息会算一个同步完成
* @param {*} data
*/
[send](data) {
return new Promise((resolve, reject) => {
if (!this.isOpen) {
return reject('连接未打开');
}
this.queue[data[this.option.ioKey]] = ({ result, error }) => {
if (result) {
resolve(result);
} else {
reject(error);
}
};
const par = fromatPotoReq(data.traceId, data.traceType, data.content);
let isUni = false;
try {
isUni = uni;
} catch (e) {
isUni = false;
}
if (isUni) {
this.socket.send({
data: par,
fail(e) {
reject({ error: e });
},
});
} else if (WebSocket) {
this.socket.send(par);
}
});
}
/**
* 服务端推送消息只处理服务端主动推送的消息
* @param {*} data
*/
[onMessage](data) {
// 判断非服务端主动推送消息不做处理
if (data[this.option.ioKey] || data.code !== 200) {
return false;
}
console.log('[im] 主动接收的消息', data);
let ctx = data.content;
let historyData = [...this.sessionData],
newData = [];
const hisIndex = historyData.findIndex((i) => i.id === ctx.sessionId);
if (hisIndex >= 0) {
// 存在会话往现有会话增加一条消息,并修改最后一条消息为当前消息
const curHisData = historyData[hisIndex];
curHisData.messageList.push(ctx);
curHisData.lastMessage = ctx;
// 不在当前会话窗口则向会话消息加1条未读
if (ctx.sessionId !== this.curSessionId) {
curHisData.unreadCount++;
}
newData = historyData;
} else {
// 会话列表不存在,则创建一个会话
newData = [
...historyData,
{
fromAvatar: ctx.fromAvatar,
fromId: ctx.fromId,
fromNickname: ctx.fromNickname,
id: ctx.sessionId,
lastMessage: ctx,
messageList: [ctx],
updateTimeStamp : ctx.createTimeStamp,
unreadCount: 1,
},
];
}
this.setSessionData(newData);
}
init(config) {
return new Promise((resolve, reject) => {
const heart = () => {
// 要优化 心跳没回复需要重连
setTimeout(async () => {
if (this.isOpen) {
await this[send]({
traceId: CreateUUID(),
traceType: 0,
content: { text: 'ping' },
});
}
heart();
}, 1000);
};
this.option = {
...this.option,
...config,
};
this[connect]()
.then((res) => {
resolve(res);
heart();
})
.catch((e) => {
console.log('eeeee', e);
});
});
}
/**
* 设置数据
*/
setSessionData(data) {
let newData = JSON.parse(JSON.stringify(data));
this.interceptors.dataChangeBefore && this.interceptors.dataChangeBefore(newData, this.sessionData);
this.sessionData = newData;
this.interceptors.dataChangeAfter && this.interceptors.dataChangeAfter(this.sessionData);
}
/**
* 设置当前聊天窗口
* Data为Session数据
* @param {*} data
*/
setCurSessionId(id) {
this.curSessionId = id;
}
/**
* 获取会话列表
* @param {*} params
*/
async getSessionList(params) {
const par = {
traceId: CreateUUID(),
traceType: 1,
...params,
};
console.log('[im] 获取会话列表--start', par);
let { error, result } = await ToAsyncAwait(this[send](par));
console.log('[im] 获取会话列表--end', result, error);
if (error) {
return Promise.reject(error);
}
const { content } = result;
content.sessionVOS.forEach((item) => {
if (item.lastMessage) {
item.lastMessage.payload = JSON.parse(item.lastMessage.payload || {});
}
let historyData = this.sessionData;
let hisIndex = historyData.findIndex((i) => i.id === item.id);
if (hisIndex >= 0) {
historyData[hisIndex].lastMessage = item.lastMessage;
historyData[hisIndex].unreadCount++;
this.setSessionData(historyData);
} else {
item.messageList = [];
const newData = [...historyData, item];
this.setSessionData(newData);
}
});
return Promise.resolve(result);
}
/**
* 获取会话的历史消息记录
* @param {*} params
*/
async getHistoryMsg() {
const curSessionIdx = this.sessionData.findIndex((i) => i.id === this.curSessionId);
const curSession = this.sessionData[curSessionIdx];
const msgList = curSession.messageList || [];
const par = {
traceId: CreateUUID(),
traceType: 2,
content: {
sessionId: this.curSessionId,
topMessageId: msgList.length ? msgList[0].id : null,
},
};
console.log('[im] 获取会话历史消息--start', par);
const { error, result } = await ToAsyncAwait(this[send](par));
console.log('[im] 获取会话历史消息--end', result, error);
if (error) {
return Promise.reject(error);
}
const { content } = result;
if (content.length) {
let newData = this.sessionData;
content.forEach((item) => {
item.payload = JSON.parse(item.payload);
});
newData[curSessionIdx].messageList = content.concat(newData[curSessionIdx].messageList);
this.setSessionData(newData);
}
return Promise.resolve(result);
}
/**
* 会话已读
* @param {*} params
*/
async setRead(params) {
const par = {
traceId: CreateUUID(),
traceType: '6',
...params,
};
console.log('[im] 会话已读--start', par);
const { error, result } = await this[send](par);
console.log('[im] 会话已读--end', result, error);
let newData = this.sessionData.map((item) => {
if (item.id == params.content.sessionId) {
item.unreadCount = 0;
}
return item;
});
this.setSessionData(newData);
}
/**
* 发送消息
* @param {*} params
*/
async sendMsg(params) {
const index = this.sessionData.findIndex((i) => i.id === this.curSessionId);
let curSession = this.sessionData[index];
// 临时消息体
let par = {
...params,
traceId: CreateUUID(),
traceType: 3,
};
let msgCtx = {
...params.content,
...par,
createTimeStamp: new Date().getTime(),
sendStatus: 'loading',
};
if (typeof msgCtx.payload === 'string') {
msgCtx.payload = JSON.parse(msgCtx.payload)
}
// 点发送,立即把消息加入消息列表,标记为发送中状态
curSession.messageList.push(msgCtx);
// 超过时间未返回视为发送失败
this.timerStatus(msgCtx);
console.log('[im] 发送消息--start', par);
const { error, result } = await ToAsyncAwait(this[send](par));
console.log('[im] 发送消息--end', result, error);
// 接到通知,标记消息是否发送成功
for (let i = curSession.messageList.length; i--; ) {
const item = curSession.messageList[i];
if (item[this.option.ioKey] === par[this.option.ioKey]) {
curSession.messageList[i].sendStatus = msgCtx.sendStatus = error ? 'fail' : 'success';
break;
}
}
let newData = [...this.sessionData];
newData[index] = curSession;
this.setSessionData(newData);
if (error) {
return Promise.reject(error);
}
return Promise.resolve(result);
}
/**
* 发送失败时重新发送
* @param {*} params
*/
async resend(params) {
params.sendStatus = 'loading';
this.timerStatus(params);
console.log('[im] 重新发送消息--start', params);
const { error, result } = await ToAsyncAwait(
this[send]({
traceId: params.traceId,
traceType: params.traceType,
content: params.content,
})
);
console.log('[im] 重新发送消息--end', result, error);
params.createTimeStamp = result.createTimeStamp;
if (error) {
params.sendStatus = 'fail';
return Promise.reject(error);
}
params.sendStatus = 'success';
return Promise.resolve(result);
}
timerStatus(msg) {
setTimeout(() => {
if (msg.sendStatus === 'loading') {
msg.sendStatus = 'fail';
delete this.queue[msg.traceId];
}
}, 3000);
}
/**
* 主动创建会话
* @param {*} params
*/
async createSession(params) {
const par = {
traceId: CreateUUID(),
traceType: 9,
...params,
};
console.log('[im] 主动创建会话--start', par);
const { result, error } = await ToAsyncAwait(this[send](par));
console.log('[im] 主动创建会话--end', result, error);
if (error) {
return Promise.reject(error);
}
const { content } = result;
let historyData = this.sessionData;
let curSession = historyData.find((i) => i.id === content.id);
if (!curSession) {
curSession = {
...content,
unreadCount: 0,
messageList: [],
};
const newData = [...historyData, curSession];
this.setSessionData(newData);
}
return Promise.resolve(result);
}
close() {
this.socket.close();
this.socket = null;
this.isOpen = false;
this.setSessionData([]);
}
}
export default MsbIm;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -2,7 +2,7 @@
* @Author: ch * @Author: ch
* @Date: 2022-05-04 21:57:26 * @Date: 2022-05-04 21:57:26
* @LastEditors: ch * @LastEditors: ch
* @LastEditTime: 2022-05-27 11:07:04 * @LastEditTime: 2022-06-12 14:40:19
* @Description: file content * @Description: file content
*/ */
@ -11,8 +11,8 @@ import {
toAsyncAwait as ToAsyncAwait, toAsyncAwait as ToAsyncAwait,
isPhone as IsPhone, isPhone as IsPhone,
formatDate as FormatDate, formatDate as FormatDate,
creatUuid as CreatUuid, formatJsonSearch as FormatJsonSearch,
toSearchJson as ToSearchJson creatUuid as CreateUUID
} from "js-util-all" } from "js-util-all"
/** /**
@ -45,9 +45,9 @@ export {
// 时间格式化 // 时间格式化
FormatDate, FormatDate,
// 创建UUID // 创建UUID
CreatUuid, CreateUUID,
// 请求Search参数转化为JSON格式 // 请求Search参数转化为JSON格式
ToSearchJson, FormatJsonSearch,
// 防抖函数 // 防抖函数
Debounce Debounce
} }
Loading…
Cancel
Save