消息发送

feat-im-0607-ch
ch 2 years ago
parent 3f501f0cce
commit e593b5e7dd

@ -2,4 +2,7 @@ src/assets
src/icons
public
dist
node_modules
node_modules
src/plugins/msb-im.js
src/utils/proto-rsq.js
src/utils/proto-rsp.js

7017
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -0,0 +1,23 @@
/*
* @Author: ch
* @Date: 2022-06-07 15:41:05
* @LastEditors: ch
* @LastEditTime: 2022-06-08 14:45:05
* @Description: file content
*/
const state = {
sessionData: [],
};
const getters = {};
const mutations = {
SET_SESSION_DATA(state, data) {
state.sessionData = data;
},
};
const actions = {};
export default {
state,
getters,
mutations,
actions,
};

@ -0,0 +1,58 @@
/*
* @Author: ch
* @Date: 2022-06-07 15:52:37
* @LastEditors: ch
* @LastEditTime: 2022-06-08 14:51:11
* @Description: file content
*/
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',
});
await ToAsyncAwait(
Im.init({
url: `ws://192.168.10.94:8090/ws${par}`,
})
);
};
Im.interceptors.dataChangeAfter = () => {
$store.commit('im/SET_SESSION_DATA', JSON.parse(JSON.stringify(Im.sessionData)));
// let msgCount = 0;
// Im.sessionData.forEach((i) => {
// msgCount += i.unreadCount;
// });
// $store.commit('SET_IM_MSG_COUNT', msgCount);
};
Im.interceptors.onLogout = () => {
Im.setSessionData([]);
// Im.setCurSessionId(null);
$store.commit('im/SET_SESSION_DATA', []);
// $store.commit('SET_IM_MSG_COUNT', 0);
};
export { Im, ImInit };

@ -0,0 +1,456 @@
/*
* @Author: ch
* @Date: 2022-05-18 14:54:47
* @LastEditors: ch
* @LastEditTime: 2022-06-08 15:10:31
* @Description: file content
*/
import '@/utils/poto-req';
import '@/utils/proto-rsp';
import { CreateUUID, ToAsyncAwait } from '@/utils/utils';
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,
logout: false,
};
socket = null;
isOpen = false;
queue = {};
interceptors = {
dataChangeBefore: null,
dataChangeAfter: null,
onLogout: 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);
// 没有401了要优化
if (result.code === 401) {
this.logout();
return false;
}
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');
if (this.option.reconnect && !this.option.logout) {
this[connect]();
}
this.option.logout = false;
};
if (WebSocket) {
try {
this.socket = new WebSocket(option.url);
this.socket.binaryType = 'arraybuffer';
this.socket.onopen = () => {
open();
};
this.socket.onmessage = (res) => {
message(res);
};
this.socket.onclose = () => {
close();
};
} 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();
});
}
});
}
/**
* 向服务端发送消息||请求返回一个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);
if (WebSocket) {
this.socket.send(par);
} else if (uni) {
this.socket.send({
data: par,
fail(e) {
reject({ error: e });
},
});
}
});
}
/**
* 服务端推送消息只处理服务端主动推送的消息
* @param {*} data
*/
[onMessage](data) {
// 判断非服务端主动推送消息不做处理
if (data[this.option.ioKey] || data.code !== 200) {
return false;
}
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.id,
lastMessage: ctx,
messageList: [ctx],
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[connect]({
...config,
})
.then((res) => {
resolve(res);
heart();
})
.catch((e) => {
console.log('eeeee', e);
});
});
}
logout() {
this.option.logout = true;
this.socket.close();
this.interceptors.onLogout && this.interceptors.onLogout();
}
/**
* 设置数据
*/
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);
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);
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);
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,
fromId: params.fromId,
createTimeStamp: new Date().getTime(),
sendStatus: 'loading',
};
// 点发送,立即把消息加入消息列表,标记为发送中状态
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);
// 接到通知,标记消息是否发送成功
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);
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: 21,
...params,
};
console.log('[im] 主动创建会话--start', par);
const { result, error } = await ToAsyncAwait(this[send](par));
console.log('[im] 主动创建会话--start', result);
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);
}
}
export default MsbIm;
// export default (app) => {
// app.config.globalProperties.$im = MsbIm;
// };

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

@ -0,0 +1,36 @@
/*
* @Author: ch
* @Date: 2022-06-07 11:34:12
* @LastEditors: ch
* @LastEditTime: 2022-06-07 16:12:42
* @Description: file content
*/
//生成UUID
const CreateUUID = () => {
let d = _.now();
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
var r = (d + _.random(16)) % 16 | 0;
d = Math.floor(d / 16);
return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);
});
};
const ToAsyncAwait = (promise, fromatResult = true) => {
if (!fromatResult) {
return promise;
}
return promise.then((res) => ({ error: null, result: res })).catch((err) => ({ error: err, result: null }));
};
const FormatJsonSearch = (myJson, connector = '?') => {
if (myJson.constructor !== Object) {
throw new Error('必须是JSON对象');
}
let arr = [];
for (let i in myJson) {
arr.push(`${i}=${encodeURIComponent(myJson[i])}`);
}
return `${connector}${arr.join('&')}`;
};
export { CreateUUID, ToAsyncAwait, FormatJsonSearch };

@ -0,0 +1,475 @@
<template>
<div class="chat-container" @click="emojiVisible = false">
<div class="header">
<p>当前客服{{ userInfo?.employeeName }}</p>
<!-- <el-button type="text" @click="summaryVisible = !summaryVisible">
{{ summaryVisible ? '收起' : '统计数据' }}
</el-button> -->
</div>
<div class="summary">
<el-table v-show="summaryVisible" border :data="[summary]">
<el-table-column align="center" header-align="center" label="今日接待次数" prop="todayTimes" />
<el-table-column align="center" header-align="center" label="今日接待人数" prop="todayCount" />
<el-table-column align="center" header-align="center" label="历史接待次数" prop="historyTimes" />
<el-table-column align="center" header-align="center" label="历史接待人数" prop="historyCount" />
</el-table>
</div>
<div class="body">
<div class="aside">
<div class="aside-header">近期会话</div>
<el-scrollbar class="session-list" tag="ul">
<li
v-for="(item, index) in sessionList"
:key="index"
class="session-item"
:class="{ active: currentSessionId === item.id }"
@click="handleChangeSession(item.id)"
>
<el-badge
class="session-count"
:hidden="item.unreadCount === 0"
:max="99"
:value="item.unreadCount"
>
<el-avatar circle :src="item.fromAvatar" />
</el-badge>
<div class="session-info">
<div class="row">
<div class="session-name">{{ item.fromNickname }}</div>
<div class="session-time">
{{ store.getters['chat/parseTime'](item.lastMessage.createTimeStamp) }}
</div>
</div>
<div class="row">
<div class="session-content">
{{ store.getters['chat/parseText'](item.lastMessage) }}
</div>
</div>
</div>
</li>
</el-scrollbar>
</div>
<div v-if="currentSession" class="content">
<div class="content-header">
<div class="content-header-left">
<div class="name" :class="{ [`sex-` + currentSession?.fromSex]: true }">
{{ currentSession?.fromNickname }}
</div>
</div>
<div class="content-header-right">
<el-button @click="handleOrder">
<el-icon name="clipboard" />
个人订单
</el-button>
<el-button @click="handleTransferSession">
<el-icon name="chat-forward" />
转移会话
</el-button>
</div>
</div>
<el-scrollbar ref="refsMessageList" class="message-list">
<el-button v-if="sessionMessageList.length" class="load" type="text" @click="handleLoadMore">
加载更多
</el-button>
<message-item
v-for="(item, index) in sessionMessageList"
:key="index"
:message="item"
:session="currentSession"
/>
</el-scrollbar>
<div class="operation-bar">
<el-button type="text" @click.stop="emojiVisible = !emojiVisible">
<el-icon name="chat-smile-3-fill" size="20" />
</el-button>
<el-button type="text" @click="handlePickImage">
<el-icon name="image-fill" size="20" />
</el-button>
<el-button type="text" @click="handlePickVideo">
<el-icon name="movie-fill" size="20" />
</el-button>
<el-scrollbar v-show="emojiVisible" class="emoji-panel" tag="ul">
<li v-for="(item, index) in emojiList" :key="index" @click="handleAddEmoji(item)">
{{ entitiestoUtf16(item) }}
</li>
</el-scrollbar>
<input
ref="refsImage"
accept="image/*"
style="display: none"
type="file"
@change="handleSendImage"
/>
<input
ref="refsVideo"
accept="video/*"
style="display: none"
type="file"
@change="handleSendVideo"
/>
</div>
<div class="input">
<el-input
v-model="state.message"
placeholder="请输入要发送的内容shift+enter换行enter发送"
type="textarea"
@keypress.enter.prevent="handleSendMessage"
/>
</div>
<div class="send">
<el-button type="primary" @click="handleSendMessage()"></el-button>
</div>
</div>
<div v-else class="content empty">请点击左侧会话列表与买家进行聊天</div>
</div>
<el-dialog v-model="transferVisible" title="转移会话">
<table-list
code="TransferCustomerService"
:config="transferConfig"
:data="customerServiceList"
:operation="[]"
style="height: 500px"
/>
</el-dialog>
</div>
</template>
<script setup lang="jsx">
import { upload } from '@/api/file';
import { emojiData, entitiestoUtf16 } from '@/utils/chat.js';
import { ElButton } from 'element-plus/es/components/button/index';
import 'element-plus/es/components/button/style/css';
import MessageItem from './message.vue';
const { proxy } = getCurrentInstance();
const router = useRouter();
const store = useStore();
store.dispatch('chat/connect');
store.dispatch('chat/querySession');
const opts = computed(() => store.state.chat.opts);
//
const userInfo = computed(() => store.state.auth.userInfo);
const summaryVisible = ref(false);
const summary = ref({
historyTimes: 0,
historyCount: 0,
todayTimes: 0,
todayCount: 0,
});
//
const currentSessionId = computed(() => store.state.chat.currentSession);
const state = reactive({
message: '',
});
const sessionList = computed(() => {
return store.state.chat.sessionData?.sessionVOS || [];
});
const currentSession = computed(() => sessionList.value.find((item) => item.id === currentSessionId.value));
const handleChangeSession = (id) => {
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(() => store.state.chat.messageList);
const refsMessageList = ref(null);
watch(sessionMessageList, (value, old) => {
let offset = refsMessageList.value
? refsMessageList.value.resize$.scrollHeight - refsMessageList.value.resize$.scrollTop
: 0;
nextTick(() => {
if (!old?.length || value.indexOf(old[0]) === 0) {
refsMessageList.value.setScrollTop(refsMessageList.value.resize$.scrollHeight);
} else {
refsMessageList.value.setScrollTop(refsMessageList.value.resize$.scrollHeight - offset);
}
});
});
const handleLoadMore = () => {
store.dispatch('chat/querySessionMessage', { topMessageId: unref(sessionMessageList)[0].id });
};
const handleSendMessage = (e) => {
if (e && e.shiftKey) {
state.message += '\n';
} else {
if (state.message) {
store.dispatch('chat/submitMessage', { text: state.message });
state.message = '';
} else {
proxy.$message.warning('发送消息不能为空');
}
}
};
//
const handleOrder = () => {
router.push({ name: 'OrderManagement', query: { id: currentSession.value.fromId } });
};
//
const transferVisible = ref(false);
const transferConfig = ref({
page: false,
columns: [
{
label: '账号',
prop: 'account',
width: 160,
},
{
label: '客户昵称',
prop: 'nickname',
minWidth: 160,
},
{
label: '类型',
prop: 'type',
width: 160,
slots: {
default: ({ row }) => proxy.$dict(unref(opts).customerServiceType, row.type),
},
},
{
label: '操作',
width: 100,
slots: {
default: ({ row }) => (
<ElButton type="text" onClick={() => handleConfirmTransfer(row)}>
转移
</ElButton>
),
},
},
],
});
const customerServiceList = computed(() => store.state.chat.customerServiceList);
const handleTransferSession = () => {
store.dispatch('chat/queryCustomerService');
transferVisible.value = true;
};
const handleConfirmTransfer = async (row) => {
try {
let res = await proxy.$prompt('请输入转移原因', '转移会话', {
confirmButtonText: '确定',
});
if (res.action === 'confirm') {
store.dispatch('chat/submitTransferSession', {
toWaiterId: row.userId,
sessionId: unref(currentSessionId),
reason: res.value,
});
transferVisible.value = false;
}
} catch (e) {
console.info('取消转移会话', e);
}
};
//
const emojiVisible = ref(false);
const emojiList = computed(() => emojiData.map((item) => item.list).flat());
const handleAddEmoji = (data) => {
let str = ' ' + entitiestoUtf16(data) + ' ';
state.message += str;
};
//
const refsImage = ref(null);
const handlePickImage = () => {
refsImage.value.click();
};
const handleSendImage = async (e) => {
const file = e.target.files[0];
e.target.value = null;
let url = await upload('mall-product', 'im/', file);
store.dispatch('chat/submitImage', { url });
};
//
const refsVideo = ref(null);
const handlePickVideo = () => {
refsVideo.value.click();
};
const handleSendVideo = async (e) => {
const file = e.target.files[0];
e.target.value = null;
let url = await upload('mall-product', 'im/', file);
store.dispatch('chat/submitVideo', { url });
};
</script>
<style lang="less" scoped>
.chat-container {
display: flex;
flex-direction: column;
overflow: hidden;
.header {
display: flex;
align-items: center;
padding: @layout-space 0;
.el-button {
margin-left: @layout-space-super;
}
}
.body {
width: 100%;
flex: 1;
overflow: hidden;
display: flex;
border: 1px solid #ebeef5;
.aside {
border-right: 1px solid #ebeef5;
.aside-header {
height: 60px;
display: flex;
align-items: center;
padding: @layout-space;
border-bottom: 1px solid #ebeef5;
font-size: @layout-h3;
font-weight: bolder;
}
.session-list {
display: flex;
flex-direction: column;
.session-item {
display: flex;
align-items: center;
padding: @layout-space;
cursor: pointer;
&:hover {
background-color: #f5f5f5;
}
&.active {
background-color: #eee;
}
.session-count {
margin-right: @layout-space;
:deep(.el-badge__content) {
top: calc(@layout-space / 2);
right: calc(@layout-space * 1.25);
}
}
.session-info {
overflow: hidden;
margin-left: @layout-space;
.row {
display: flex;
justify-content: space-between;
}
.session-time,
.session-content {
font-size: 0.8em;
color: #999;
}
.session-content {
width: 150px;
margin-top: @layout-space-small;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
}
+ .session-item {
border-top: 1px solid #ebeef5;
}
}
}
}
.content {
flex: 1;
display: flex;
flex-direction: column;
&.empty {
justify-content: center;
align-items: center;
color: #999;
}
.content-header {
height: 60px;
display: flex;
align-items: center;
justify-content: space-between;
padding: @layout-space;
border-bottom: 1px solid #ebeef5;
.content-header-left {
display: flex;
align-items: center;
.name {
font-size: @layout-h3;
font-weight: bolder;
&.sex-0::before {
content: '♀';
color: var(--el-color-danger);
font-weight: bolder;
font-family: '黑体';
}
&.sex-1::before {
content: '♂';
color: var(--el-color-primary);
font-weight: bolder;
font-family: '黑体';
}
}
}
}
}
.message-list {
flex: 1;
background-color: #f1f1f1;
.load {
width: 100%;
}
}
.operation-bar {
display: flex;
align-items: center;
padding: 0 @layout-space;
border-top: 1px solid #ebeef5;
border-bottom: 1px solid #ebeef5;
position: relative;
.emoji-panel {
position: absolute;
z-index: 9999;
border-radius: 8px;
overflow: auto;
box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);
bottom: 52px;
width: 300px;
height: 200px;
padding: 10px 6px 10px 10px;
background: #eee;
font-size: 20px;
text-align: center;
:deep(ul) {
display: grid;
grid-template-columns: repeat(auto-fill, 30px);
column-gap: auto;
li {
cursor: pointer;
}
}
}
}
.input {
height: 64px;
.el-textarea {
height: 100%;
:deep(textarea) {
height: 100% !important;
box-shadow: none;
}
}
}
.send {
display: flex;
justify-content: flex-end;
padding: @layout-space-small;
}
}
}
</style>

@ -68,11 +68,16 @@
</div>
</div>
<el-scrollbar ref="refsMessageList" class="message-list">
<el-button v-if="sessionMessageList.length" class="load" type="text" @click="handleLoadMore">
<el-button
v-if="currentSession.messageList?.length"
class="load"
type="text"
@click="handleLoadMore"
>
加载更多
</el-button>
<message-item
v-for="(item, index) in sessionMessageList"
v-for="(item, index) in currentSession.messageList"
:key="index"
:message="item"
:session="currentSession"
@ -137,14 +142,18 @@
<script setup lang="jsx">
import { upload } from '@/api/file';
import { emojiData, entitiestoUtf16 } from '@/utils/chat.js';
import { Im, ImInit } from '@/utils/im.js';
import { ElButton } from 'element-plus/es/components/button/index';
import 'element-plus/es/components/button/style/css';
import MessageItem from './message.vue';
const { proxy } = getCurrentInstance();
const router = useRouter();
const store = useStore();
store.dispatch('chat/connect');
store.dispatch('chat/querySession');
ImInit().then(() => {
Im.getSessionList();
});
// store.dispatch('chat/connect');
// store.dispatch('chat/querySession');
const opts = computed(() => store.state.chat.opts);
//
@ -158,25 +167,38 @@
});
//
const currentSessionId = computed(() => store.state.chat.currentSession);
let currentSessionId = ref();
const state = reactive({
message: '',
});
const sessionList = computed(() => {
return store.state.chat.sessionData?.sessionVOS || [];
});
const currentSession = computed(() => sessionList.value.find((item) => item.id === currentSessionId.value));
const sessionList = computed(() => store.state.im.sessionData);
const currentSession = computed(() => sessionList.value.find((item) => item.id === currentSessionId.value) || []);
const handleChangeSession = (id) => {
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');
currentSessionId.value = id;
// ID
Im.setCurSessionId(id);
//
Im.setRead({
content: {
sessionId: id,
},
});
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(() => store.state.chat.messageList);
const sessionMessageList = computed(() => {
console.log(currentSession.messageList?.length, 'currentSession');
return currentSession.messageList || [];
});
const refsMessageList = ref(null);
watch(sessionMessageList, (value, old) => {
let offset = refsMessageList.value
@ -191,14 +213,23 @@
});
});
const handleLoadMore = () => {
store.dispatch('chat/querySessionMessage', { topMessageId: unref(sessionMessageList)[0].id });
Im.getHistoryMsg();
// store.dispatch('chat/querySessionMessage', { topMessageId: unref(sessionMessageList)[0].id });
};
const handleSendMessage = (e) => {
if (e && e.shiftKey) {
state.message += '\n';
} else {
if (state.message) {
store.dispatch('chat/submitMessage', { text: state.message });
Im.sendMsg({
fromId: 2, //userInfo.value.id,
content: {
toSessionId: currentSessionId.value,
payload: { text: state.message },
type: 1,
},
});
// store.dispatch('chat/submitMessage', { text: state.message });
state.message = '';
} else {
proxy.$message.warning('发送消息不能为空');
@ -287,7 +318,16 @@
const file = e.target.files[0];
e.target.value = null;
let url = await upload('mall-product', 'im/', file);
store.dispatch('chat/submitImage', { url });
// store.dispatch('chat/submitImage', { url });
Im.sendMsg({
fromId: 2, //userInfo.value.id,
content: {
toSessionId: currentSessionId.value,
payload: { url },
type: 3,
},
});
};
//
@ -299,7 +339,16 @@
const file = e.target.files[0];
e.target.value = null;
let url = await upload('mall-product', 'im/', file);
store.dispatch('chat/submitVideo', { url });
// store.dispatch('chat/submitVideo', { url });
Im.sendMsg({
fromId: 2, //userInfo.value.id,
content: {
toSessionId: currentSessionId.value,
payload: { url },
type: 4,
},
});
};
</script>

@ -85,7 +85,7 @@ export default (configEnv) => {
sourcePath: 'src/styles',
}),
legacy({
targets: ['defaults', 'not IE 11'],
targets: ['defaults', 'not IE 11', 'chrome 52'],
}),
{
...eslintPlugin({

Loading…
Cancel
Save