2021-10-27 19:15:39 +08:00

547 lines
9.9 KiB
Vue

<template>
<div v-loading="!visible && loading" class="cl-chat-message" element-loading-text="消息加载中">
<div
:ref="setRefs('scroller')"
class="cl-chat-message__scroller scroller1"
:style="{
opacity: visible ? 1 : 0
}"
>
<!-- 加载更多 -->
<div v-show="list.length > 0" class="cl-chat-message__more">
<el-button round size="mini" :loading="loading" @click="onLoadmore"
>加载更多</el-button
>
</div>
<!-- 消息列表 -->
<div class="cl-chat-message__list">
<div
v-for="item in list"
:key="item.id || item.uid"
class="cl-chat-message__item"
:class="[item.type == 0 ? `is-right` : `is-left`, `is-${item.mode}`]"
>
<!-- 日期 -->
<div v-if="item._date" class="date">
<span>{{ item._date }}</span>
</div>
<!-- 内容 -->
<div class="main">
<!-- 头像 -->
<div class="avatar">
<img :src="item.avatarUrl" />
</div>
<div class="det">
<!-- 昵称 -->
<span class="name">{{ item.nickName }}</span>
<div
v-loading="item.loading"
class="content"
:element-loading-text="item.progress"
@click="onTap(item)"
>
<!-- 文本 -->
<template v-if="item.mode === 'text'">{{
item.content.text
}}</template>
<!-- 图片 -->
<template v-else-if="item.mode === 'image'">
<el-image
:key="item.uid"
:src="item.content.imageUrl"
:preview-src-list="[item.content.imageUrl]"
:z-index="3000"
:style="item.style"
>
<template #placeholder>
<img
:src="item.content.imageUrl"
:style="item.style"
alt=""
/>
</template>
</el-image>
</template>
<!-- 表情 -->
<template v-else-if="item.mode === 'emoji'">
<img :src="item.content.imageUrl" />
</template>
<!-- 语音 -->
<template v-else-if="item.mode === 'voice'">
<icon-voice :play="item.isPlay" />
<span class="duration">{{ item.content.duration }}"</span>
</template>
<!-- 视频 -->
<template v-else-if="item.mode === 'video'">
<div class="item">
<video
:poster="item.content.videoUrl"
:src="item.content.videoUrl"
controls
></video>
</div>
</template>
<!-- 未知 -->
<template v-else>
<span>待扩展消息类型</span>
<i class="el-icon-warning-outline"></i>
</template>
</div>
</div>
</div>
</div>
<!-- 音频 -->
<div class="voice">
<audio ref="voice" style="display: none" :src="voice.url" controls></audio>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, inject, nextTick, onUnmounted, reactive, ref } from "vue";
import dayjs from "dayjs";
import { ElMessage } from "element-plus";
import { isString } from "/@/core/utils";
import { useCool } from "/@/core";
import IconVoice from "./icon-voice.vue";
import AvatarUrl from "../static/images/custom-avatar.png";
export default defineComponent({
components: {
IconVoice
},
setup() {
const { refs, setRefs, service, store, mitt } = useCool();
// 聊天信息
const chat = inject<any>("chat");
// 当前会话信息
const session = computed(() => store.getters.session);
// 加载状态
const loading = ref<boolean>(false);
// 是否可见
const visible = ref<boolean>(false);
// 分页信息
const pagination = reactive<any>({
page: 1,
size: 20,
total: 0
});
// 语音
const voice = reactive<any>({
url: "",
timer: null
});
// 请求随机值
const refreshRd = ref<any>(null);
// 消息列表
const list = computed(() => {
const { userInfo, messageList } = store.getters;
let date = "";
return messageList.map((e: any) => {
// 时间间隔
const _date = date
? dayjs(e.createTime).isBefore(dayjs(date).add(1, "minute"))
? ""
: e.createTime
: e.createTime;
// 发送时间
date = e.createTime;
// 解析内容
if (isString(e)) {
e = JSON.parse(e);
}
// 内容
const content = isString(e.content) ? JSON.parse(e.content) : e.content;
// 昵称
const nickName = e.type == 0 ? userInfo.nickName : session.value.nickname;
// 头像
const avatarUrl =
e.type == 0 ? userInfo.avatarUrl || AvatarUrl : session.value.headimgurl;
return {
...e,
_date,
content,
avatarUrl,
nickName,
mode: chat.modes[e.contentType]
};
});
});
// 点击
function onTap(item: any) {
// 播放语音
if (item.mode == "voice") {
store.getters.messageList.map((e: any) => {
e.isPlay = e.id == item.id ? e.isPlay : false;
});
item.isPlay = !item.isPlay;
if (item.isPlay) {
voice.url = item.content.voiceUrl;
nextTick(() => {
refs.value.voice.play();
});
} else {
refs.value.voice.pause();
item.isPlay = false;
}
clearTimeout(voice.timer);
voice.timer = setTimeout(() => {
item.isPlay = false;
}, item.content.duration);
}
}
// 滚动到底部
function scrollToBottom() {
nextTick(() => {
if (refs.value.scroller) {
refs.value.scroller.scrollTo({
top: 99999,
behavior: visible.value ? "smooth" : "auto"
});
}
});
}
// 刷新列表
function refresh(params?: any) {
// 请求随机值
const rd = (refreshRd.value = Math.random());
// 请求参数
const data = {
...pagination,
...params,
sessionId: session.value.id,
order: "createTime",
sort: "desc"
};
// 加载动画
loading.value = true;
// 首页处理
if (data.page === 1) {
visible.value = false;
store.commit("CLEAR_MESSAGE_LIST");
}
// 完成
const done = () => {
loading.value = false;
visible.value = true;
};
service.chat.message
.page(data)
.then((res: any) => {
// 防止脏数据
if (rd != refreshRd.value) {
return false;
}
// 分页信息
Object.assign(pagination, res.pagination);
// 追加数据
store.commit("PREPEND_MESSAGE_LIST", res.list);
if (data.page === 1) {
scrollToBottom();
// 首次滚动隐藏
setTimeout(done, 0);
} else {
done();
}
})
.catch((err: string) => {
ElMessage.error(err);
done();
});
}
// 加载更多
function onLoadmore() {
refresh({ page: pagination.page + 1 });
}
// 监听列表刷新
mitt.on("message.refresh", refresh);
// 滚动到底部
mitt.on("message.scrollToBottom", scrollToBottom);
// 销毁
onUnmounted(function () {
// 移除语音播放
clearTimeout(voice.timer);
list.value.map((e: any) => {
e.isPlay = false;
});
// 移除事件
mitt.off("message.refresh", refresh);
mitt.off("message.scrollToBottom", scrollToBottom);
});
return {
refs,
chat,
session,
loading,
visible,
pagination,
voice,
list,
setRefs,
onTap,
refresh,
onLoadmore,
scrollToBottom
};
}
});
</script>
<style lang="scss" scoped>
.cl-chat-message {
height: calc(100% - 5px);
overflow: hidden;
margin-bottom: 5px;
&__scroller {
height: calc(100% - 10px);
border-radius: 5px;
margin: 5px 0px 5px 5px;
padding: 10px;
box-sizing: border-box;
}
&__more {
display: flex;
justify-content: center;
margin-bottom: 20px;
}
&__item {
margin-bottom: 20px;
.date {
text-align: center;
margin: 10px 0;
span {
background-color: #dadada;
font-size: 12px;
color: #fff;
border-radius: 3px;
padding: 3px 5px 2px 5px;
letter-spacing: 1px;
}
}
.main {
display: flex;
.avatar {
flex-shrink: 0;
img {
display: block;
height: 40px;
width: 40px;
border-radius: 3px;
background-color: #fff;
}
}
.det {
display: flex;
flex-direction: column;
max-width: 60%;
.name {
margin-bottom: 5px;
}
.content {
display: inline-block;
border-radius: 8px;
box-sizing: border-box;
font-size: 12px;
}
}
}
&.is-left {
.main {
.det {
margin-left: 10px;
align-items: flex-start;
.content {
border-top-left-radius: 0;
background-color: #fff;
}
}
}
&.is-voice {
.content {
justify-content: flex-start;
}
}
}
&.is-right {
.main {
flex-direction: row-reverse;
.det {
margin-right: 10px;
align-items: flex-end;
.content {
border-top-right-radius: 0;
background-color: $color-primary;
color: #fff;
}
}
}
&.is-voice {
.content {
justify-content: flex-end;
}
}
}
&.is-text {
.content {
max-width: 100%;
min-width: 40px;
word-wrap: break-word;
}
}
&.is-text,
&.is-voice {
.content {
padding: 10px;
line-height: 20px;
letter-spacing: 1px;
}
}
&.is-emoji {
.content {
padding: 10px;
img {
height: 20px;
width: 20px;
}
}
}
&.is-voice {
.content {
display: flex;
align-items: center;
width: 65px;
cursor: pointer;
&:hover {
opacity: 0.8;
}
}
}
&.is-video {
.item {
video {
display: block;
max-width: 300px;
max-height: 300px;
border-radius: 10px;
}
}
}
&.is-image {
.main {
.det {
.content {
background-color: #fff;
.el-image {
display: block;
border-radius: 6px;
max-width: 200px;
min-width: 80px;
}
}
}
}
}
&.is-undefined {
.main {
.det {
.content {
display: flex;
align-items: center;
padding: 10px;
letter-spacing: 1px;
background-color: #f56c6c;
color: #fff;
.el-icon-warning-outline {
font-size: 15px;
margin-left: 4px;
}
}
}
}
}
}
}
</style>