You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
shop-admin/src/views/chat/index copy.vue

476 lines
18 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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