feat: 消息列表对接

feature/task1.0.0__0514__ch
向文可 2 years ago
parent 78628cc8a7
commit 1d807ab18f

@ -7,25 +7,37 @@ const state = () => ({
queue: [], queue: [],
task: [], task: [],
sessionData: {}, sessionData: {},
currentSession: null,
messageList: [],
messageType: ['', 'text'],
}); });
const getters = {}; const getters = {
parseTime: () => {
return (timestamp) => {
dayjs(new Date(timestamp)).format('MM-DD HH:mm:ss');
};
},
parseContent: () => {
return (payload) => {
payload = JSON.parse(payload);
if ('text' in payload) {
payload = payload.text;
} else {
payload = '[未知消息]';
}
return payload;
};
},
};
const mutations = { const mutations = {
setSocket: (state, data) => (state.socket = data), setSocket: (state, data) => (state.socket = data),
setHeart: (state, data) => (state.heart = data), setHeart: (state, data) => (state.heart = data),
setTask: (state, data) => (state.task = data), setTask: (state, data) => (state.task = data),
addTask: (state, data) => state.task.push(data), addTask: (state, data) => state.task.push(data),
delTask: (state, data) => state.task.splice(data, 1), delTask: (state, data) => state.task.splice(data, 1),
receive: (state, { code, traceType, content }) => { setCurrentSession: (state, data) => (state.currentSession = data),
if (code === 200) { setSessionData: (state, data) => (state.sessionData = data),
switch (traceType) { setMessageList: (state, data) => (state.messageList = data),
case 27:
state.sessionData = content;
break;
default:
break;
}
}
},
}; };
const actions = { const actions = {
/** /**
@ -36,17 +48,7 @@ const actions = {
if (window.WebSocket) { if (window.WebSocket) {
const socket = new WebSocket('ws://192.168.10.93:8090/ws?client=' + token); const socket = new WebSocket('ws://192.168.10.93:8090/ws?client=' + token);
socket.onmessage = ({ data }) => { socket.onmessage = ({ data }) => {
data = JSON.parse(data); dispatch('receive', data);
if (data.traceType !== 0) {
let index = state.task.findIndex((item) => item.traceId === data.traceId);
if (index !== -1) {
commit('delTask', index);
commit('receive', data);
console.info('[chat] msg', data);
} else {
console.info('[chat] deprecated', data);
}
}
}; };
socket.onopen = () => { socket.onopen = () => {
commit( commit(
@ -110,7 +112,47 @@ const actions = {
} }
}, },
/** /**
* 查询回话列表 * 接收数据
*/
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) {
commit('delTask', index);
dispatch('handle', data);
console.info('[chat] msg', data);
} else {
console.info('[chat] deprecated', data);
}
}
},
handle: ({ state, commit, dispatch }, { code, traceType, content, session }) => {
if (code === 200) {
switch (traceType) {
// 收到消息
case 25:
if (session.id === state.currentSession) {
commit('setMessageList', [...state.messageList, content.content]);
} else {
dispatch('querySession');
}
break;
// 会话列表
case 27:
commit('setSessionData', content);
break;
// 消息列表
case 28:
commit('setMessageList', content);
break;
default:
break;
}
}
},
/**
* 查询会话列表
*/ */
querySession: ({ dispatch }) => { querySession: ({ dispatch }) => {
dispatch('invoke', { dispatch('invoke', {
@ -118,6 +160,15 @@ const actions = {
content: { storeId: 1 }, content: { storeId: 1 },
}); });
}, },
/**
* 查询会话消息列表
*/
querySessionMessage: ({ state, dispatch }) => {
dispatch('invoke', {
traceType: 28,
content: { sessionId: state.currentSession, size: 100, topMessageId: null },
});
},
}; };
export default { export default {
state, state,

@ -1,6 +1,5 @@
<template> <template>
<div class="chat-container" @click="emojiVisible = false"> <div class="chat-container" @click="emojiVisible = false">
{{ $store.state.chat }}
<div class="header"> <div class="header">
<p>当前客服小爱</p> <p>当前客服小爱</p>
<el-button type="text" @click="summaryVisible = !summaryVisible"> <el-button type="text" @click="summaryVisible = !summaryVisible">
@ -23,8 +22,8 @@
v-for="(item, index) in sessionList" v-for="(item, index) in sessionList"
:key="index" :key="index"
class="session-item" class="session-item"
:class="{ active: state.currentIndex === index }" :class="{ active: currentSessionId === item.id }"
@click="handleChangeSession(index)" @click="handleChangeSession(item.id)"
> >
<el-badge <el-badge
class="session-count" class="session-count"
@ -37,20 +36,24 @@
<div class="session-info"> <div class="session-info">
<div class="row"> <div class="row">
<div class="session-name">{{ item.fromNickname }}</div> <div class="session-name">{{ item.fromNickname }}</div>
<div class="session-time">{{ parseTime(item.lastMessage.createTimeStamp) }}</div> <div class="session-time">
{{ store.getters['chat/parseTime'](item.lastMessage.createTimeStamp) }}
</div>
</div> </div>
<div class="row"> <div class="row">
<div class="session-content">{{ parseContent(item.lastMessage.payload) }}</div> <div class="session-content">
{{ store.getters['chat/parseContent'](item.lastMessage.payload) }}
</div>
</div> </div>
</div> </div>
</li> </li>
</el-scrollbar> </el-scrollbar>
</div> </div>
<div class="content"> <div v-if="currentSession" class="content">
<div class="content-header"> <div class="content-header">
<div class="content-header-left"> <div class="content-header-left">
<div class="name sex-1"> <div class="name" :class="{ [`sex-` + currentSession?.fromSex]: true }">
{{ currentSession?.name }} {{ currentSession?.fromNickname }}
</div> </div>
</div> </div>
<div class="content-header-right"> <div class="content-header-right">
@ -65,7 +68,12 @@
</div> </div>
</div> </div>
<el-scrollbar ref="refsMessageList" class="message-list"> <el-scrollbar ref="refsMessageList" class="message-list">
<message-item v-for="(item, index) in sessionMessageList" :key="index" :message="item" /> <message-item
v-for="(item, index) in sessionMessageList"
:key="index"
:message="item"
:session="currentSession"
/>
</el-scrollbar> </el-scrollbar>
<div class="operation-bar"> <div class="operation-bar">
<el-button type="text" @click.stop="emojiVisible = !emojiVisible"> <el-button type="text" @click.stop="emojiVisible = !emojiVisible">
@ -104,6 +112,7 @@
<el-button type="primary" @click="handleSendMessage"></el-button> <el-button type="primary" @click="handleSendMessage"></el-button>
</div> </div>
</div> </div>
<div v-else class="content empty">请点击左侧会话列表与买家进行聊天</div>
</div> </div>
</div> </div>
</template> </template>
@ -125,106 +134,18 @@
}); });
// //
const currentSessionId = computed(() => store.state.chat.currentSession);
const state = reactive({ const state = reactive({
currentIndex: 0,
message: '', message: '',
}); });
const sessionList = computed(() => { const sessionList = computed(() => {
return store.state.chat.sessionData?.sessionVOS || []; return store.state.chat.sessionData?.sessionVOS || [];
}); });
const currentSession = computed(() => { const currentSession = computed(() => sessionList.value.find((item) => item.id === currentSessionId.value));
return sessionList[state.currentIndex]; const sessionMessageList = computed(() => store.state.chat.messageList);
}); const handleChangeSession = (id) => {
// store.commit('chat/setCurrentSession', id);
const parseTime = (timestamp) => dayjs(new Date(timestamp)).format('MM-DD HH:mm:ss'); store.dispatch('chat/querySessionMessage');
const parseContent = (payload) => {
payload = JSON.parse(payload);
if ('text' in payload) {
payload = payload.text;
} else {
payload = '[未知消息]';
}
return payload;
};
const sessionMessageList = reactive([
{
type: 'text',
content: '你好,请问这个能有优惠吗?',
time: '2019-12-12',
from: 'customer',
avatar: 'https://placem.at/people',
name: '小可爱',
},
{
type: 'notify',
content: '小马 将该会话转移给 小艾,并留言:发货问题',
time: '2019-12-12',
from: 'system',
},
{
type: 'text',
content: '没有',
time: '2019-12-12',
from: 'employee',
avatar: 'https://placem.at/people',
name: '小爱',
},
{
type: 'product',
content: `{
"id": 18,
"name": "柏战K20青轴机械键盘 混光机械键盘青轴电竞网吧游戏键盘电脑键盘",
"categoryId": 51,
"startingPrice": 79.0,
"totalStock": 1200,
"mainPicture": "https://msb-edu-dev.oss-cn-beijing.aliyuncs.com/mall-product/productO1CN011nFEoP1DrheUNhNgU_!!3435280270-0-cib.jpg",
"remoteAreaPostage": 10.0,
"singleBuyLimit": 0,
"isEnable": true,
"remark": "柏战K20青轴机械键盘 混光机械键盘青轴电竞网吧游戏键盘电脑键盘"
}`,
time: '2019-12-12',
from: 'customer',
avatar: 'https://placem.at/people',
name: '小可爱',
},
{
type: 'order',
content: `{
"orderId": 392016,
"orderNo": "6391f99d063e",
"userId": 513,
"userPhone": "17683712911",
"payAmount": 859.0,
"orderStatus": 2,
"orderStatusDesc": "已关闭",
"payType": 1,
"payTypeDesc": "未支付",
"orderSource": 2,
"orderSourceDesc": "安卓端APP",
"submitTime": "2022-05-10 20:36:59",
"product": {
"id": 18,
"name": "柏战K20青轴机械键盘 混光机械键盘青轴电竞网吧游戏键盘电脑键盘",
"categoryId": 51,
"startingPrice": 79.0,
"totalStock": 1200,
"mainPicture": "https://msb-edu-dev.oss-cn-beijing.aliyuncs.com/mall-product/productO1CN011nFEoP1DrheUNhNgU_!!3435280270-0-cib.jpg",
"remoteAreaPostage": 10.0,
"singleBuyLimit": 0,
"isEnable": true,
"remark": "柏战K20青轴机械键盘 混光机械键盘青轴电竞网吧游戏键盘电脑键盘"
}
}`,
time: '2019-12-12',
from: 'customer',
avatar: 'https://placem.at/people',
name: '小可爱',
},
]);
const handleChangeSession = (index) => {
state.currentIndex = index;
}; };
// //
@ -384,6 +305,11 @@
flex: 1; flex: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
&.empty {
justify-content: center;
align-items: center;
color: #999;
}
.content-header { .content-header {
height: 60px; height: 60px;
display: flex; display: flex;

@ -2,16 +2,16 @@
<div <div
class="message-item" class="message-item"
:class="{ :class="{
[`--${props.message.type}`]: true, [`--${messageType[props.message.type]}`]: true,
'--self': props.message.from === 'employee', '--self': props.message.fromId === props.session.fromId,
}" }"
> >
<div v-if="props.message.avatar" class="avatar"> <div class="avatar">
<el-avatar :src="props.message.avatar" /> <el-avatar :src="props.session.fromAvatar" />
</div> </div>
<div class="message-body"> <div class="message-body">
<div class="name"> <div class="name">
{{ props.message.name }} {{ props.session.fromNickname }}
</div> </div>
<el-card v-if="props.message.type === 'product'" class="shadow"> <el-card v-if="props.message.type === 'product'" class="shadow">
<template #header> <template #header>
@ -59,23 +59,54 @@
<el-button>查看详情</el-button> <el-button>查看详情</el-button>
</div> </div>
</el-card> </el-card>
<div v-else class="content" :class="{ shadow: props.message.type === 'text' }"> <div v-else class="content" :class="{ shadow: messageType[props.message.type] === 'text' }">
{{ props.message.content }} {{ store.getters['chat/parseContent'](props.message.payload) }}
</div> </div>
</div> </div>
<div v-if="props.message.type !== 'notify'" class="time"> <div v-if="props.message.type !== 'notify'" class="time">
{{ props.message.time }} {{ store.getters['chat/parseTime'](props.message.createTimeStamp) }}
</div> </div>
</div> </div>
</template> </template>
<script setup> <script setup>
/**
* 消息体结构
{
"code": 200,
"content": {
"createTimeStamp": "消息的创建时间戳",
"fromId": "消息发送人id",
"id": "消息id",
"payload": "{'text':'你好我是客服小王'}",
"traceId": "uuid",
"traceType": 23,
"sessionId": "消息所属会话id",
"messageIndex": "会话中消息的顺序",
"type": "消息的类型"
},
"session": {
"fromAvatar": "消息所属会话的对面人头像",
"fromId": "消息所属会话的对面人id",
"fromNickname": "消息所属会话的对面人昵称",
"id": "消息id",
"sysId": "系统id",
"type": "消息类型"
}
}
*/
const store = useStore();
const props = defineProps({ const props = defineProps({
session: {
type: Object,
required: true,
},
message: { message: {
type: Object, type: Object,
required: true, required: true,
}, },
}); });
const messageType = computed(() => store.state.chat.messageType);
const product = computed(() => { const product = computed(() => {
return props.message.type === 'product' ? JSON.parse(props.message.content) : {}; return props.message.type === 'product' ? JSON.parse(props.message.content) : {};
}); });

Loading…
Cancel
Save