feat: 聊天交互

feature/task1.0.0__0514__ch
向文可 3 years ago
parent 76eb6645e0
commit 4c92155014

@ -1,11 +1,30 @@
<template> <template>
<div class="chat-container"> <div class="chat-container" @click="emojiVisible = false">
<div class="header">当前客服小爱</div> <div class="header">
<p>当前客服小爱</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="body">
<div class="aside"> <div class="aside">
<div class="aside-header">近期会话</div> <div class="aside-header">近期会话</div>
<el-scrollbar class="session-list" tag="ul"> <el-scrollbar class="session-list" tag="ul">
<li v-for="(item, index) in sessionList" :key="index" class="session-item"> <li
v-for="(item, index) in sessionList"
:key="index"
class="session-item"
:class="{ active: state.currentIndex === index }"
@click="handleChangeSession(index)"
>
<el-badge class="session-count" :hidden="item.count === 0" :max="99" :value="item.count"> <el-badge class="session-count" :hidden="item.count === 0" :max="99" :value="item.count">
<el-avatar circle :src="item.avatar" /> <el-avatar circle :src="item.avatar" />
</el-badge> </el-badge>
@ -29,34 +48,54 @@
</div> </div>
</div> </div>
<div class="content-header-right"> <div class="content-header-right">
<el-button>个人订单</el-button> <el-button>
<el-button>转移会话</el-button> <el-icon name="clipboard" />
个人订单
</el-button>
<el-button>
<el-icon name="chat-forward" />
转移会话
</el-button>
</div> </div>
</div> </div>
<el-scrollbar 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" />
</el-scrollbar> </el-scrollbar>
<div class="operation-bar"> <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"> <el-scrollbar v-show="emojiVisible" class="emoji-panel" tag="ul">
<li v-for="(item, index) in emojiList" :key="index" @click="handleAddEmoji(item)"> <li v-for="(item, index) in emojiList" :key="index" @click="handleAddEmoji(item)">
{{ entitiestoUtf16(item) }} {{ entitiestoUtf16(item) }}
</li> </li>
</el-scrollbar> </el-scrollbar>
<el-button type="text" @click="emojiVisible = !emojiVisible"> <input
<el-icon name="chat-smile-3-fill" size="24" /> ref="refsImage"
</el-button> accept="image/*"
<el-button type="text"> style="display: none"
<el-icon name="image-fill" size="24" /> type="file"
</el-button> @change="handleSendImage"
<el-button type="text"> />
<el-icon name="movie-fill" size="24" /> <input
</el-button> ref="refsVideo"
accept="video/*"
style="display: none"
type="file"
@change="handleSendVideo"
/>
</div> </div>
<div class="input"> <div class="input">
<el-input v-model="state.message" type="textarea" /> <el-input v-model="state.message" placeholder="请输入要发送的内容" type="textarea" />
</div> </div>
<div class="send"> <div class="send">
<el-button type="primary">发送</el-button> <el-button type="primary" @click="handleSendMessage"></el-button>
</div> </div>
</div> </div>
</div> </div>
@ -66,6 +105,22 @@
<script setup> <script setup>
import { emojiData, entitiestoUtf16 } from '@/utils/emoji.js'; import { emojiData, entitiestoUtf16 } from '@/utils/emoji.js';
import MessageItem from './message.vue'; import MessageItem from './message.vue';
const { proxy } = getCurrentInstance();
//
const summaryVisible = ref(true);
const summary = ref({
historyTimes: 96,
historyCount: 88,
todayTimes: 10,
todayCount: 8,
});
//
const state = reactive({
currentIndex: 0,
message: '',
});
const sessionList = reactive([ const sessionList = reactive([
{ {
name: '小可爱', name: '小可爱',
@ -89,10 +144,6 @@
content: '你好,请问这个能有优惠吗?', content: '你好,请问这个能有优惠吗?',
}, },
]); ]);
const state = reactive({
currentIndex: 0,
message: '',
});
const currentSession = computed(() => { const currentSession = computed(() => {
return sessionList[state.currentIndex]; return sessionList[state.currentIndex];
}); });
@ -172,13 +223,80 @@
name: '小可爱', name: '小可爱',
}, },
]); ]);
const handleChangeSession = (index) => {
state.currentIndex = index;
};
//
const emojiVisible = ref(false); const emojiVisible = ref(false);
const emojiList = computed(() => emojiData.map((item) => item.list).flat()); const emojiList = computed(() => emojiData.map((item) => item.list).flat());
const handleAddEmoji = (data) => { const handleAddEmoji = (data) => {
let str = ' ' + entitiestoUtf16(data) + ' '; let str = ' ' + entitiestoUtf16(data) + ' ';
state.message += str; state.message += str;
emojiVisible.value = false; };
//
const refsImage = ref(null);
const handlePickImage = () => {
refsImage.value.click();
};
const handleSendImage = (e) => {
const file = e.target.files[0];
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = (e) => {
const data = e.target.result;
const message = {
type: 'image',
content: data,
};
handleSendMessage(message);
};
};
//
const refsVideo = ref(null);
const handlePickVideo = () => {
refsVideo.value.click();
};
const handleSendVideo = (e) => {
const file = e.target.files[0];
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = (e) => {
const data = e.target.result;
const message = {
type: 'video',
content: data,
};
handleSendMessage(message);
};
};
//
const refsMessageList = ref(null);
const handleSendMessage = (data) => {
const message = data || {
type: 'text',
content: state.message,
};
if (message.content) {
Object.assign(message, {
time: dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss'),
from: 'employee',
avatar: 'https://placem.at/people',
name: '小爱',
});
sessionMessageList.push(message);
if (!data) {
state.message = '';
}
nextTick(() => {
refsMessageList.value.setScrollTop(refsMessageList.value.scrollbar$.scrollHeight);
});
} else {
proxy.$message.warning('发送消息不能为空');
}
}; };
</script> </script>
@ -188,22 +306,30 @@
flex-direction: column; flex-direction: column;
overflow: hidden; overflow: hidden;
.header { .header {
display: flex;
align-items: center;
padding: @layout-space 0; padding: @layout-space 0;
.el-button {
margin-left: @layout-space-super;
}
}
.summary {
margin-bottom: @layout-space;
} }
.body { .body {
width: 100%; width: 100%;
flex: 1; flex: 1;
overflow: hidden; overflow: hidden;
display: flex; display: flex;
border: 1px solid #eee; border: 1px solid #ebeef5;
.aside { .aside {
border-right: 1px solid #eee; border-right: 1px solid #ebeef5;
.aside-header { .aside-header {
height: 60px; height: 60px;
display: flex; display: flex;
align-items: center; align-items: center;
padding: @layout-space; padding: @layout-space;
border-bottom: 1px solid #eee; border-bottom: 1px solid #ebeef5;
font-size: @layout-h3; font-size: @layout-h3;
font-weight: bolder; font-weight: bolder;
} }
@ -218,6 +344,9 @@
&:hover { &:hover {
background-color: #f5f5f5; background-color: #f5f5f5;
} }
&.active {
background-color: #eee;
}
.session-count { .session-count {
margin-right: @layout-space; margin-right: @layout-space;
:deep(.el-badge__content) { :deep(.el-badge__content) {
@ -246,7 +375,7 @@
} }
} }
+ .session-item { + .session-item {
border-top: 1px solid #eee; border-top: 1px solid #ebeef5;
} }
} }
} }
@ -261,7 +390,7 @@
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
padding: @layout-space; padding: @layout-space;
border-bottom: 1px solid #eee; border-bottom: 1px solid #ebeef5;
.content-header-left { .content-header-left {
display: flex; display: flex;
align-items: center; align-items: center;
@ -291,9 +420,9 @@
.operation-bar { .operation-bar {
display: flex; display: flex;
align-items: center; align-items: center;
padding: @layout-space; padding: 0 @layout-space;
border-top: 1px solid #eee; border-top: 1px solid #ebeef5;
border-bottom: 1px solid #eee; border-bottom: 1px solid #ebeef5;
position: relative; position: relative;
.emoji-panel { .emoji-panel {
position: absolute; position: absolute;
@ -319,7 +448,7 @@
} }
} }
.input { .input {
height: 100px; height: 64px;
.el-textarea { .el-textarea {
height: 100%; height: 100%;
:deep(textarea) { :deep(textarea) {

Loading…
Cancel
Save