mirror of
https://github.com/cool-team-official/cool-admin-vue.git
synced 2025-12-12 21:52:48 +00:00
优化客服聊天
This commit is contained in:
parent
9c96709e1e
commit
38d07a3361
@ -1,155 +1,21 @@
|
||||
<template>
|
||||
<div class="cl-chat__wrap">
|
||||
<!-- 聊天窗口 -->
|
||||
<cl-dialog :visible.sync="visible" v-bind="conf">
|
||||
<cl-dialog
|
||||
:visible.sync="visible"
|
||||
:title="title"
|
||||
:height="height"
|
||||
:width="width"
|
||||
:props="conf"
|
||||
>
|
||||
<div class="cl-chat">
|
||||
<!-- 会话区域 -->
|
||||
<div class="cl-chat__session">
|
||||
<div class="cl-chat__session-search">
|
||||
<el-input
|
||||
v-model="session.keyWord"
|
||||
placeholder="搜索"
|
||||
prefix-icon="el-icon-search"
|
||||
size="small"
|
||||
clearable
|
||||
@clear="onSearch"
|
||||
@keyup.enter.native="onSearch"
|
||||
></el-input>
|
||||
</div>
|
||||
|
||||
<!-- 会话列表 -->
|
||||
<ul class="cl-chat__session-list scroller1" v-if="sessionList.length > 0">
|
||||
<li
|
||||
class="cl-chat__session-item"
|
||||
v-for="(item, index) in sessionList"
|
||||
:key="index"
|
||||
:class="{
|
||||
'is-active': session.current ? item.id == session.current.id : false
|
||||
}"
|
||||
@click="sessionDetail(item)"
|
||||
@contextmenu.stop.prevent="openSessionCM($event, item.id, index)"
|
||||
>
|
||||
<!-- 头像 -->
|
||||
<div class="avatar">
|
||||
<el-badge
|
||||
:value="item.serviceUnreadCount"
|
||||
:hidden="item.serviceUnreadCount === 0"
|
||||
:max="99"
|
||||
>
|
||||
<img :src="item.headimgurl" alt="" />
|
||||
</el-badge>
|
||||
</div>
|
||||
|
||||
<!-- 昵称,内容 -->
|
||||
<div class="det">
|
||||
<p class="name">{{ item.nickname }}</p>
|
||||
<p class="content">{{ item.lastMessage }}</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- 空态 -->
|
||||
<div class="cl-chat__session-empty" v-else>
|
||||
没有搜索到内容...
|
||||
</div>
|
||||
</div>
|
||||
<chat-session />
|
||||
|
||||
<!-- 会话详情 -->
|
||||
<div class="cl-chat__detail">
|
||||
<template v-if="session.current">
|
||||
<div
|
||||
class="cl-chat__detail-container scroller1"
|
||||
ref="scroller"
|
||||
v-loading="message.loading"
|
||||
>
|
||||
<!-- 加载更多 -->
|
||||
<div class="cl-chat__detail-more" v-if="message.list.length > 0">
|
||||
<el-button
|
||||
round
|
||||
size="mini"
|
||||
:loading="message.loading"
|
||||
@click="onLoadmore"
|
||||
>加载更多</el-button
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- 消息列表 -->
|
||||
<message :list="message.list" />
|
||||
</div>
|
||||
|
||||
<div class="cl-chat__detail-footer">
|
||||
<!-- 工具栏 -->
|
||||
<div class="cl-chat__opbar">
|
||||
<ul>
|
||||
<!-- 表情 -->
|
||||
<li>
|
||||
<el-popover
|
||||
v-model="emoji.visible"
|
||||
placement="top-start"
|
||||
width="470"
|
||||
trigger="click"
|
||||
>
|
||||
<emoji @select="onEmojiSelect" />
|
||||
<img
|
||||
slot="reference"
|
||||
src="../static/images/emoji.png"
|
||||
alt=""
|
||||
/>
|
||||
</el-popover>
|
||||
</li>
|
||||
<!-- 图片上传 -->
|
||||
<li hidden>
|
||||
<cl-upload
|
||||
accept="image/*"
|
||||
list-type
|
||||
:on-success="onImageSelect"
|
||||
>
|
||||
<img src="../static/images/image.png" alt="" />
|
||||
</cl-upload>
|
||||
</li>
|
||||
<!-- 视频上传 -->
|
||||
<li hidden>
|
||||
<cl-upload
|
||||
accept="video/*"
|
||||
list-type
|
||||
:before-upload="
|
||||
f => {
|
||||
onBeforeUpload(f, 'video');
|
||||
}
|
||||
"
|
||||
:on-progress="onUploadProgress"
|
||||
:on-success="
|
||||
(r, f) => {
|
||||
onUploadSuccess(r, f, 'video');
|
||||
}
|
||||
"
|
||||
>
|
||||
<img src="../static/images/video.png" alt="" />
|
||||
</cl-upload>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- 输入框,发送按钮 -->
|
||||
<div class="cl-chat__input">
|
||||
<el-input
|
||||
v-model="message.value"
|
||||
placeholder="请描述您想咨询的问题"
|
||||
type="textarea"
|
||||
:rows="5"
|
||||
@keyup.enter.native="onTextSend"
|
||||
></el-input>
|
||||
|
||||
<el-button
|
||||
type="primary"
|
||||
size="mini"
|
||||
:disabled="!message.value"
|
||||
@click="onTextSend"
|
||||
>发送</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="cl-chat__detail" v-if="session">
|
||||
<chat-message />
|
||||
<chat-input />
|
||||
</div>
|
||||
</div>
|
||||
</cl-dialog>
|
||||
@ -164,22 +30,36 @@
|
||||
<script>
|
||||
import dayjs from "dayjs";
|
||||
import { mapGetters } from "vuex";
|
||||
import { isString, debounce } from "cl-admin/utils";
|
||||
import io from "socket.io-client";
|
||||
import { socketUrl } from "@/config/env";
|
||||
import Emoji from "./emoji";
|
||||
import Message from "./message";
|
||||
import { parseContent } from "../utils";
|
||||
|
||||
import io from "socket.io-client";
|
||||
import { socketUrl } from "@/config/env";
|
||||
|
||||
import Session from "./session";
|
||||
import Message from "./message";
|
||||
import Input from "./input";
|
||||
import eventBus from "../utils/event-bus";
|
||||
|
||||
// 消息模式
|
||||
const MODES = ["text", "image", "emoji", "voice", "video"];
|
||||
|
||||
export default {
|
||||
name: "cl-chat",
|
||||
|
||||
components: {
|
||||
Message,
|
||||
Emoji
|
||||
"chat-session": Session,
|
||||
"chat-message": Message,
|
||||
"chat-input": Input
|
||||
},
|
||||
|
||||
props: {
|
||||
height: {
|
||||
type: String,
|
||||
default: "650px"
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: "1000px"
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
@ -187,55 +67,25 @@ export default {
|
||||
visible: false,
|
||||
socket: null,
|
||||
conf: {
|
||||
title: "聊天对话框",
|
||||
height: "650px",
|
||||
width: "1000px",
|
||||
props: {
|
||||
modal: true,
|
||||
customClass: "cl-chat__dialog",
|
||||
"append-to-body": true,
|
||||
"close-on-click-modal": false
|
||||
}
|
||||
},
|
||||
message: {
|
||||
list: [],
|
||||
pagination: {
|
||||
page: 1,
|
||||
size: 20,
|
||||
total: 0
|
||||
},
|
||||
loading: false,
|
||||
value: ""
|
||||
},
|
||||
session: {
|
||||
list: [],
|
||||
pagination: {
|
||||
page: 1,
|
||||
size: 100,
|
||||
total: 0
|
||||
},
|
||||
current: null,
|
||||
keyWord: ""
|
||||
},
|
||||
emoji: {
|
||||
visible: false
|
||||
modal: true,
|
||||
customClass: "cl-chat__dialog",
|
||||
"append-to-body": true,
|
||||
"close-on-click-modal": false
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters(["userInfo", "token"]),
|
||||
provide() {
|
||||
return {
|
||||
socket: this.socket
|
||||
};
|
||||
},
|
||||
|
||||
sessionList() {
|
||||
return this.session.list
|
||||
.map(e => {
|
||||
let { _text } = parseContent(e);
|
||||
e.lastMessage = _text;
|
||||
return e;
|
||||
})
|
||||
.sort((a, b) => {
|
||||
return a.updateTime < b.updateTime ? 1 : -1;
|
||||
});
|
||||
computed: {
|
||||
...mapGetters(["token", "session", "sessionList"]),
|
||||
|
||||
title() {
|
||||
return this.session ? `与 ${this.session.nickname} 聊天中` : "聊天对话框";
|
||||
}
|
||||
},
|
||||
|
||||
@ -264,227 +114,12 @@ export default {
|
||||
methods: {
|
||||
open() {
|
||||
this.visible = true;
|
||||
|
||||
this.refreshSession().then(res => {
|
||||
this.sessionDetail(res.list[0]);
|
||||
});
|
||||
},
|
||||
|
||||
close() {
|
||||
this.visible = false;
|
||||
},
|
||||
|
||||
// 上传前
|
||||
onBeforeUpload(file, key) {
|
||||
const data = {
|
||||
content: {
|
||||
[`${key}Url`]: ""
|
||||
},
|
||||
type: 0,
|
||||
contentType: MODES.indexOf(key),
|
||||
uid: file.uid,
|
||||
loading: true,
|
||||
progress: "0%"
|
||||
};
|
||||
|
||||
this.append(data);
|
||||
},
|
||||
|
||||
// 上传中
|
||||
onUploadProgress(e, file) {
|
||||
let item = this.message.list.find(e => e.uid == file.uid);
|
||||
|
||||
if (item) {
|
||||
item.progress = e.percent + "%";
|
||||
}
|
||||
},
|
||||
|
||||
// 上传成功
|
||||
onUploadSuccess(res, file, key) {
|
||||
let item = this.message.list.find(e => e.uid == file.uid);
|
||||
|
||||
if (item) {
|
||||
item.loading = false;
|
||||
item.content[`${key}Url`] = res.data;
|
||||
|
||||
this.sendMessage(item);
|
||||
}
|
||||
},
|
||||
|
||||
// 打开会话列表右键菜单
|
||||
openSessionCM(e, id, index) {
|
||||
this.$crud.openContextMenu(e, {
|
||||
list: [
|
||||
{
|
||||
label: "删除",
|
||||
icon: "el-icon-delete",
|
||||
callback: (_, done) => {
|
||||
this.$service.im.session.delete({
|
||||
ids: id
|
||||
});
|
||||
|
||||
this.session.list.splice(index, 1);
|
||||
|
||||
if (id == this.session.current.id) {
|
||||
this.sessionDetail();
|
||||
}
|
||||
|
||||
done();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
},
|
||||
|
||||
// 刷新会话列表
|
||||
refreshSession(params) {
|
||||
return this.$service.im.session
|
||||
.page({
|
||||
...this.session.pagination,
|
||||
keyWord: this.session.keyWord,
|
||||
params,
|
||||
order: "updateTime",
|
||||
sort: "desc"
|
||||
})
|
||||
.then(res => {
|
||||
this.session.list = res.list;
|
||||
this.session.pagination = res.pagination;
|
||||
|
||||
return res;
|
||||
});
|
||||
},
|
||||
|
||||
// 刷新详情
|
||||
async sessionDetail(item) {
|
||||
if (item) {
|
||||
let { id } = this.session.current || {};
|
||||
|
||||
if (id != item.id) {
|
||||
item.serviceUnreadCount = 0;
|
||||
|
||||
this.conf.title = `与${item.nickname}聊天中`;
|
||||
this.message.loading = true;
|
||||
this.message.list = [];
|
||||
this.session.current = item;
|
||||
|
||||
await this.refreshMessage({ page: 1 });
|
||||
|
||||
this.message.loading = false;
|
||||
}
|
||||
|
||||
this.scrollToBottom();
|
||||
} else {
|
||||
this.conf.title = "聊天对话框";
|
||||
this.message.list = [];
|
||||
this.session.current = null;
|
||||
}
|
||||
},
|
||||
|
||||
// 刷新消息列表
|
||||
refreshMessage(params) {
|
||||
return this.$service.im.message
|
||||
.page({
|
||||
...this.message.pagination,
|
||||
...params,
|
||||
sessionId: this.session.current.id,
|
||||
order: "createTime",
|
||||
sort: "desc"
|
||||
})
|
||||
.then(res => {
|
||||
this.message.pagination = res.pagination;
|
||||
this.prepend.apply(this, res.list);
|
||||
});
|
||||
},
|
||||
|
||||
// 更新会话消息
|
||||
updateSession(data) {
|
||||
Object.assign(this.session.current, data);
|
||||
},
|
||||
|
||||
// 搜索关键字
|
||||
onSearch() {
|
||||
this.refreshSession({ page: 1 });
|
||||
},
|
||||
|
||||
// 加载更多
|
||||
onLoadmore() {
|
||||
this.refreshMessage({ page: this.message.pagination.page + 1 });
|
||||
},
|
||||
|
||||
// 滚动到底部
|
||||
scrollToBottom: debounce(function() {
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs["scroller"]) {
|
||||
this.$refs["scroller"].scrollTo({
|
||||
top: 99999,
|
||||
behavior: "smooth"
|
||||
});
|
||||
}
|
||||
});
|
||||
}, 300),
|
||||
|
||||
// 发送文本内容
|
||||
onTextSend() {
|
||||
if (this.message.value) {
|
||||
if (this.message.value.replace(/\n/g, "") !== "") {
|
||||
const data = {
|
||||
type: 0,
|
||||
contentType: 0,
|
||||
content: {
|
||||
text: this.message.value
|
||||
}
|
||||
};
|
||||
|
||||
this.append(data);
|
||||
this.sendMessage(data);
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.message.value = "";
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 图片选择
|
||||
onImageSelect(res) {
|
||||
const data = {
|
||||
content: {
|
||||
imageUrl: res.data
|
||||
},
|
||||
type: 0,
|
||||
contentType: 1
|
||||
};
|
||||
this.append(data);
|
||||
this.sendMessage(data);
|
||||
},
|
||||
|
||||
// 表情选择
|
||||
onEmojiSelect(url) {
|
||||
this.emoji.visible = false;
|
||||
const data = {
|
||||
content: {
|
||||
imageUrl: url
|
||||
},
|
||||
type: 0,
|
||||
contentType: 2
|
||||
};
|
||||
this.append(data);
|
||||
this.sendMessage(data);
|
||||
},
|
||||
|
||||
// 视频选择
|
||||
onVideoSelect(url) {
|
||||
const data = {
|
||||
content: {
|
||||
videoUrl: url
|
||||
},
|
||||
type: 0,
|
||||
contentType: 4
|
||||
};
|
||||
this.append(data);
|
||||
this.sendMessage(data);
|
||||
},
|
||||
|
||||
// 监听消息
|
||||
onMessage(msg) {
|
||||
// 回调
|
||||
@ -497,17 +132,17 @@ export default {
|
||||
const { contentType, fromId, content, msgId } = JSON.parse(msg);
|
||||
|
||||
// 是否当前
|
||||
const same = this.session.current && this.session.current.userId == fromId;
|
||||
const same = this.session && this.session.userId == fromId;
|
||||
|
||||
if (same) {
|
||||
// 更新消息
|
||||
this.updateSession({
|
||||
this.$store.commit("UPDATE_SESSION", {
|
||||
contentType,
|
||||
content
|
||||
});
|
||||
|
||||
// 追加消息
|
||||
this.append({
|
||||
eventBus.$emit("message-append", {
|
||||
contentType,
|
||||
content: JSON.parse(content),
|
||||
type: 1
|
||||
@ -521,7 +156,7 @@ export default {
|
||||
}
|
||||
|
||||
// 查找会话
|
||||
let item = this.session.list.find(e => e.userId == fromId);
|
||||
const item = this.sessionList.find(e => e.userId == fromId);
|
||||
|
||||
if (item) {
|
||||
if (!same) {
|
||||
@ -535,7 +170,7 @@ export default {
|
||||
});
|
||||
} else {
|
||||
// 刷新会话列表
|
||||
this.refreshSession();
|
||||
eventBus.$emit("session-refresh");
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("消息格式异常", e);
|
||||
@ -575,76 +210,6 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 发送消息
|
||||
sendMessage({ contentType, content }) {
|
||||
const { id, userId } = this.session.current;
|
||||
|
||||
// 更新消息
|
||||
this.updateSession({
|
||||
contentType,
|
||||
content
|
||||
});
|
||||
|
||||
if (this.socket) {
|
||||
this.socket.emit(`user@${userId}`, {
|
||||
contentType,
|
||||
type: 0,
|
||||
content: JSON.stringify(content),
|
||||
sessionId: id
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 处理消息数据
|
||||
* mode: 消息模式
|
||||
* type: 消息类型 0-回复,1-反馈
|
||||
* duration: 时常
|
||||
* videoUrl: 视频地址
|
||||
* videoCoverUrl: 视频封面
|
||||
* imageUrl: 图片地址
|
||||
* avatarUrl: 头像地址
|
||||
* nickName: 昵称
|
||||
*/
|
||||
handleMessage(e) {
|
||||
if (isString(e)) {
|
||||
e = JSON.parse(e);
|
||||
}
|
||||
|
||||
if (isString(e.content)) {
|
||||
e.content = JSON.parse(e.content);
|
||||
}
|
||||
|
||||
// 昵称
|
||||
const nickName = e.type == 0 ? this.userInfo.nickName : this.session.current.nickname;
|
||||
// 头像
|
||||
const avatarUrl =
|
||||
e.type == 0
|
||||
? this.userInfo.avatarUrl || require("../static/images/custom-avatar.png")
|
||||
: this.session.current.headimgurl;
|
||||
|
||||
return {
|
||||
...e,
|
||||
avatarUrl,
|
||||
nickName,
|
||||
mode: MODES[e.contentType],
|
||||
date: dayjs().format("YYYY-MM-DD HH:mm:ss")
|
||||
};
|
||||
},
|
||||
|
||||
// 追加数据到开头
|
||||
prepend(...data) {
|
||||
data.map(this.handleMessage).forEach(e => {
|
||||
this.message.list.unshift(e);
|
||||
});
|
||||
},
|
||||
|
||||
// 追加数据到结尾
|
||||
append(...data) {
|
||||
this.message.list.push(...data.map(this.handleMessage));
|
||||
this.scrollToBottom();
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -671,90 +236,6 @@ export default {
|
||||
height: 100%;
|
||||
background-color: #f7f7f7;
|
||||
|
||||
&__session {
|
||||
height: calc(100% - 10px);
|
||||
width: 250px;
|
||||
margin: 5px 0 5px 5px;
|
||||
border-radius: 5px;
|
||||
background-color: #fff;
|
||||
|
||||
&-search {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
&-list {
|
||||
height: calc(100% - 52px);
|
||||
overflow: auto;
|
||||
|
||||
li {
|
||||
display: flex;
|
||||
list-style: none;
|
||||
padding: 10px;
|
||||
border-left: 5px solid #fff;
|
||||
|
||||
.avatar {
|
||||
margin-right: 12px;
|
||||
|
||||
img {
|
||||
display: block;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
border-radius: 3px;
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.el-badge {
|
||||
&__content {
|
||||
height: 14px;
|
||||
line-height: 14px;
|
||||
padding: 0 4px;
|
||||
background-color: #fa5151;
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.det {
|
||||
flex: 1;
|
||||
.name {
|
||||
font-size: 13px;
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
.content {
|
||||
font-size: 12px;
|
||||
margin-top: 5px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.name,
|
||||
.content {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
background-color: #eee;
|
||||
border-color: $color-primary;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #eee;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-empty {
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
&__detail {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -762,63 +243,6 @@ export default {
|
||||
height: 100%;
|
||||
padding: 5px;
|
||||
box-sizing: border-box;
|
||||
|
||||
&-container {
|
||||
flex: 1;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
overflow: auto;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
&-more {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
&-footer {
|
||||
background-color: #fff;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
&__message {
|
||||
flex: 1;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
&__opbar {
|
||||
margin-bottom: 5px;
|
||||
|
||||
ul {
|
||||
display: flex;
|
||||
li {
|
||||
list-style: none;
|
||||
margin-right: 10px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
img {
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__input {
|
||||
position: relative;
|
||||
|
||||
.el-button {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
bottom: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
265
src/cool/modules/chat/components/input.vue
Normal file
265
src/cool/modules/chat/components/input.vue
Normal file
@ -0,0 +1,265 @@
|
||||
<template>
|
||||
<div class="cl-chat-input">
|
||||
<!-- 工具栏 -->
|
||||
<div class="cl-chat-input__opbar">
|
||||
<ul>
|
||||
<!-- 表情 -->
|
||||
<li>
|
||||
<el-popover
|
||||
v-model="emoji.visible"
|
||||
placement="top-start"
|
||||
width="470"
|
||||
trigger="click"
|
||||
>
|
||||
<emoji @select="onEmojiSelect" />
|
||||
<img slot="reference" src="../static/images/emoji.png" alt="" />
|
||||
</el-popover>
|
||||
</li>
|
||||
<!-- 图片上传 -->
|
||||
<li hidden>
|
||||
<cl-upload accept="image/*" list-type :on-success="onImageSelect">
|
||||
<img src="../static/images/image.png" alt="" />
|
||||
</cl-upload>
|
||||
</li>
|
||||
<!-- 视频上传 -->
|
||||
<li hidden>
|
||||
<cl-upload
|
||||
accept="video/*"
|
||||
list-type
|
||||
:before-upload="
|
||||
f => {
|
||||
onBeforeUpload(f, 'video');
|
||||
}
|
||||
"
|
||||
:on-progress="onUploadProgress"
|
||||
:on-success="
|
||||
(r, f) => {
|
||||
onUploadSuccess(r, f, 'video');
|
||||
}
|
||||
"
|
||||
>
|
||||
<img src="../static/images/video.png" alt="" />
|
||||
</cl-upload>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- 输入框,发送按钮 -->
|
||||
<div class="cl-chat-input__content">
|
||||
<el-input
|
||||
v-model="value"
|
||||
placeholder="请描述您想咨询的问题"
|
||||
type="textarea"
|
||||
:rows="5"
|
||||
@keyup.enter.native="onTextSend"
|
||||
></el-input>
|
||||
|
||||
<el-button type="primary" size="mini" :disabled="!value" @click="onTextSend"
|
||||
>发送</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from "vuex";
|
||||
import Emoji from "./emoji";
|
||||
import eventBus from "../utils/event-bus";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Emoji
|
||||
},
|
||||
|
||||
inject: ["socket"],
|
||||
|
||||
data() {
|
||||
return {
|
||||
value: "",
|
||||
emoji: {
|
||||
visible: false
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters(["session"])
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 上传前
|
||||
onBeforeUpload(file, key) {
|
||||
const data = {
|
||||
content: {
|
||||
[`${key}Url`]: ""
|
||||
},
|
||||
type: 0,
|
||||
contentType: MODES.indexOf(key),
|
||||
uid: file.uid,
|
||||
loading: true,
|
||||
progress: "0%"
|
||||
};
|
||||
|
||||
this.append(data);
|
||||
},
|
||||
|
||||
// 上传中
|
||||
onUploadProgress(e, file) {
|
||||
const item = this.message.list.find(e => e.uid == file.uid);
|
||||
|
||||
if (item) {
|
||||
item.progress = e.percent + "%";
|
||||
}
|
||||
},
|
||||
|
||||
// 上传成功
|
||||
onUploadSuccess(res, file, key) {
|
||||
const item = this.message.list.find(e => e.uid == file.uid);
|
||||
|
||||
if (item) {
|
||||
item.loading = false;
|
||||
item.content[`${key}Url`] = res.data;
|
||||
|
||||
this.send(item);
|
||||
}
|
||||
},
|
||||
|
||||
// 发送文本内容
|
||||
onTextSend() {
|
||||
if (this.value) {
|
||||
if (this.value.replace(/\n/g, "") !== "") {
|
||||
const data = {
|
||||
type: 0,
|
||||
contentType: 0,
|
||||
content: {
|
||||
text: this.value
|
||||
}
|
||||
};
|
||||
|
||||
this.send(data, true);
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.value = "";
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 图片选择
|
||||
onImageSelect(res) {
|
||||
this.send(
|
||||
{
|
||||
content: {
|
||||
imageUrl: res.data
|
||||
},
|
||||
type: 0,
|
||||
contentType: 1
|
||||
},
|
||||
true
|
||||
);
|
||||
},
|
||||
|
||||
// 表情选择
|
||||
onEmojiSelect(url) {
|
||||
this.emoji.visible = false;
|
||||
this.send(
|
||||
{
|
||||
content: {
|
||||
imageUrl: url
|
||||
},
|
||||
type: 0,
|
||||
contentType: 2
|
||||
},
|
||||
true
|
||||
);
|
||||
},
|
||||
|
||||
// 视频选择
|
||||
onVideoSelect(url) {
|
||||
this.send(
|
||||
{
|
||||
content: {
|
||||
videoUrl: url
|
||||
},
|
||||
type: 0,
|
||||
contentType: 4
|
||||
},
|
||||
true
|
||||
);
|
||||
},
|
||||
|
||||
// 发送消息
|
||||
send(data, isAppend) {
|
||||
const { id, userId } = this.session;
|
||||
|
||||
// 更新消息
|
||||
// this.updateSession({
|
||||
// contentType,
|
||||
// content
|
||||
// });
|
||||
|
||||
if (this.socket) {
|
||||
this.socket.emit(`user@${userId}`, {
|
||||
contentType: data.contentType,
|
||||
type: 0,
|
||||
content: JSON.stringify(data.content),
|
||||
sessionId: id
|
||||
});
|
||||
|
||||
if (isAppend) {
|
||||
this.append(data);
|
||||
}
|
||||
}
|
||||
|
||||
if (isAppend) {
|
||||
this.append(data);
|
||||
}
|
||||
},
|
||||
|
||||
// 追加消息
|
||||
append(data) {
|
||||
eventBus.$emit("message-append", data);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.cl-chat-input {
|
||||
background-color: #fff;
|
||||
padding: 10px;
|
||||
border-radius: 3px;
|
||||
|
||||
&__opbar {
|
||||
margin-bottom: 5px;
|
||||
|
||||
ul {
|
||||
display: flex;
|
||||
li {
|
||||
list-style: none;
|
||||
margin-right: 10px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
img {
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
position: relative;
|
||||
|
||||
.el-button {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
bottom: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,100 +1,133 @@
|
||||
<template>
|
||||
<div class="chat-box-message">
|
||||
<div class="cl-chat-message" v-loading="loading" element-loading-text="消息加载中">
|
||||
<div
|
||||
class="chat-box-message__item"
|
||||
v-for="item in flist"
|
||||
:key="item.id || item.uid"
|
||||
:class="[item.type == 0 ? `is-right` : `is-left`, `is-${item.mode}`]"
|
||||
class="cl-chat-message__scroller scroller1"
|
||||
ref="scroller"
|
||||
:style="{
|
||||
opacity: visible ? 1 : 0
|
||||
}"
|
||||
>
|
||||
<div class="date" v-if="item._date">
|
||||
<span>{{ item._date }}</span>
|
||||
<!-- 加载更多 -->
|
||||
<div class="cl-chat-message__more" v-if="list.length > 0">
|
||||
<el-button round size="mini" :loading="loading" @click="onLoadmore"
|
||||
>加载更多</el-button
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<div class="avatar" @tap="toUserDetail(item)">
|
||||
<img :src="item.avatarUrl" />
|
||||
</div>
|
||||
<!-- 消息列表 -->
|
||||
<div class="cl-chat-message__list">
|
||||
<div
|
||||
class="cl-chat-message__item"
|
||||
v-for="item in messageList"
|
||||
:key="item.id || item.uid"
|
||||
:class="[item.type == 0 ? `is-right` : `is-left`, `is-${item.mode}`]"
|
||||
>
|
||||
<div class="date" v-if="item._date">
|
||||
<span>{{ item._date }}</span>
|
||||
</div>
|
||||
|
||||
<div class="det">
|
||||
<span class="name">{{ item.nickName }}</span>
|
||||
<div class="main">
|
||||
<div class="avatar" @tap="toUserDetail(item)">
|
||||
<img :src="item.avatarUrl" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="content"
|
||||
v-loading="item.loading"
|
||||
:element-loading-text="item.progress"
|
||||
@click="tapItem(item)"
|
||||
>
|
||||
<!-- 文本 -->
|
||||
<template v-if="item.mode === 'text'">{{ item.content.text }}</template>
|
||||
<div class="det">
|
||||
<span class="name">{{ item.nickName }}</span>
|
||||
|
||||
<!-- 图片 -->
|
||||
<template v-else-if="item.mode === 'image'">
|
||||
<el-image
|
||||
:key="item.uid"
|
||||
:src="item.content.imageUrl"
|
||||
:preview-src-list="[item.content.imageUrl]"
|
||||
></el-image>
|
||||
</template>
|
||||
<div
|
||||
class="content"
|
||||
v-loading="item.loading"
|
||||
:element-loading-text="item.progress"
|
||||
@click="onTap(item)"
|
||||
>
|
||||
<!-- 文本 -->
|
||||
<template v-if="item.mode === 'text'">{{
|
||||
item.content.text
|
||||
}}</template>
|
||||
|
||||
<!-- 表情 -->
|
||||
<template v-else-if="item.mode === 'emoji'">
|
||||
<img :src="item.content.imageUrl" />
|
||||
</template>
|
||||
<!-- 图片 -->
|
||||
<template v-else-if="item.mode === 'image'">
|
||||
<el-image
|
||||
:key="item.uid"
|
||||
:src="item.content.imageUrl"
|
||||
:preview-src-list="[item.content.imageUrl]"
|
||||
></el-image>
|
||||
</template>
|
||||
|
||||
<!-- 语音 -->
|
||||
<template v-else-if="item.mode === 'voice'">
|
||||
<icon-voice :play="item.isPlay"></icon-voice>
|
||||
<span class="duration">{{ item.content.duration | duration }}"</span>
|
||||
</template>
|
||||
<!-- 表情 -->
|
||||
<template v-else-if="item.mode === 'emoji'">
|
||||
<img :src="item.content.imageUrl" />
|
||||
</template>
|
||||
|
||||
<!-- 视频 -->
|
||||
<template v-else-if="item.mode === 'video'">
|
||||
<div class="item">
|
||||
<video
|
||||
:poster="item.content.videoUrl | video_poster"
|
||||
:src="item.content.videoUrl"
|
||||
controls
|
||||
></video>
|
||||
<!-- 语音 -->
|
||||
<template v-else-if="item.mode === 'voice'">
|
||||
<icon-voice :play="item.isPlay"></icon-voice>
|
||||
<span class="duration"
|
||||
>{{ item.content.duration | duration }}"</span
|
||||
>
|
||||
</template>
|
||||
|
||||
<!-- 视频 -->
|
||||
<template v-else-if="item.mode === 'video'">
|
||||
<div class="item">
|
||||
<video
|
||||
:poster="item.content.videoUrl | video_poster"
|
||||
:src="item.content.videoUrl"
|
||||
controls
|
||||
></video>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 未知 -->
|
||||
<template v-else>
|
||||
<span>待扩展消息类型</span>
|
||||
<i class="el-icon-warning-outline"></i>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 未知 -->
|
||||
<template v-else>
|
||||
<span>待扩展消息类型</span>
|
||||
<i class="el-icon-warning-outline"></i>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- voice -->
|
||||
<div class="voice">
|
||||
<audio style="display: none" ref="voice" :src="voice.url" controls></audio>
|
||||
<!-- voice -->
|
||||
<div class="voice">
|
||||
<audio style="display: none" ref="voice" :src="voice.url" controls></audio>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import dayjs from "dayjs";
|
||||
import { mapGetters } from "vuex";
|
||||
import { isString } from "cl-admin/utils";
|
||||
import eventBus from "../utils/event-bus";
|
||||
import IconVoice from "./icon-voice";
|
||||
|
||||
// 消息类型
|
||||
const ModeList = ["text", "image", "emoji", "voice", "video"];
|
||||
|
||||
export default {
|
||||
components: {
|
||||
IconVoice
|
||||
},
|
||||
|
||||
props: {
|
||||
list: Array
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
visible: false,
|
||||
list: [],
|
||||
pagination: {
|
||||
page: 1,
|
||||
size: 20,
|
||||
total: 0
|
||||
},
|
||||
player: {},
|
||||
voice: {
|
||||
url: "",
|
||||
timer: null
|
||||
}
|
||||
},
|
||||
refreshRd: null
|
||||
};
|
||||
},
|
||||
|
||||
@ -104,6 +137,60 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters(["userInfo", "session"]),
|
||||
|
||||
messageList() {
|
||||
let date = "";
|
||||
|
||||
return this.list.map(e => {
|
||||
// 时间间隔
|
||||
e._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);
|
||||
}
|
||||
|
||||
if (isString(e.content)) {
|
||||
e.content = JSON.parse(e.content);
|
||||
}
|
||||
|
||||
// 解析昵称
|
||||
const nickName = e.type == 0 ? this.userInfo.nickName : this.session.nickname;
|
||||
|
||||
// 解析头像
|
||||
const avatarUrl =
|
||||
e.type == 0
|
||||
? this.userInfo.avatarUrl || require("../static/images/custom-avatar.png")
|
||||
: this.session.headimgurl;
|
||||
|
||||
return {
|
||||
...e,
|
||||
avatarUrl,
|
||||
nickName,
|
||||
mode: ModeList[e.contentType]
|
||||
};
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
eventBus.$on("message-refresh", this.refresh);
|
||||
eventBus.$on("message-append", this.append);
|
||||
|
||||
if (this.session) {
|
||||
this.refresh();
|
||||
}
|
||||
},
|
||||
|
||||
destroyed() {
|
||||
clearTimeout(this.voice.timer);
|
||||
|
||||
@ -112,26 +199,10 @@ export default {
|
||||
});
|
||||
},
|
||||
|
||||
computed: {
|
||||
flist() {
|
||||
let date = "";
|
||||
|
||||
return this.list.map(e => {
|
||||
e._date = date
|
||||
? dayjs(e.createTime).isBefore(dayjs(date).add(1, "minute"))
|
||||
? ""
|
||||
: e.createTime
|
||||
: e.createTime;
|
||||
|
||||
date = e.createTime;
|
||||
|
||||
return e;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
tapItem(item) {
|
||||
// 点击
|
||||
onTap(item) {
|
||||
// 播放语音
|
||||
if (item.mode == "voice") {
|
||||
this.list.map(e => {
|
||||
this.$set(e, "isPlay", e.id == item.id ? e.isPlay : false);
|
||||
@ -156,13 +227,114 @@ export default {
|
||||
item.isPlay = false;
|
||||
}, item.content.duration);
|
||||
}
|
||||
},
|
||||
|
||||
// 刷新列表
|
||||
refresh(params) {
|
||||
// 请求随机值
|
||||
const rd = (this.refreshRd = Math.random());
|
||||
|
||||
// 请求参数
|
||||
const data = {
|
||||
...this.pagination,
|
||||
...params,
|
||||
sessionId: this.session.id,
|
||||
order: "createTime",
|
||||
sort: "desc"
|
||||
};
|
||||
|
||||
// 首页处理
|
||||
if (data.page === 1) {
|
||||
this.loading = true;
|
||||
this.visible = false;
|
||||
this.list = [];
|
||||
}
|
||||
|
||||
// 完成
|
||||
const done = () => {
|
||||
this.loading = false;
|
||||
this.visible = true;
|
||||
};
|
||||
|
||||
this.$service.im.message
|
||||
.page(data)
|
||||
.then(res => {
|
||||
// 防止脏数据
|
||||
if (rd != this.refreshRd) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 分页信息
|
||||
this.pagination = res.pagination;
|
||||
// 追加数据
|
||||
this.prepend.apply(this, res.list);
|
||||
|
||||
if (data.page === 1) {
|
||||
this.scrollToBottom();
|
||||
|
||||
// 首次滚动隐藏
|
||||
setTimeout(done, 0);
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message.error(err);
|
||||
done();
|
||||
});
|
||||
},
|
||||
|
||||
// 加载更多
|
||||
onLoadmore() {
|
||||
this.refresh({ page: this.pagination.page + 1 });
|
||||
},
|
||||
|
||||
// 滚动到底部
|
||||
scrollToBottom() {
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs["scroller"]) {
|
||||
this.$refs["scroller"].scrollTo({
|
||||
top: 99999,
|
||||
behavior: this.visible ? "smooth" : "auto"
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 追加数据到开头
|
||||
prepend(...data) {
|
||||
this.list.unshift(...data.reverse());
|
||||
},
|
||||
|
||||
// 追加数据到结尾
|
||||
append(...data) {
|
||||
this.list.push(...data);
|
||||
this.scrollToBottom();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.chat-box-message {
|
||||
.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;
|
||||
|
||||
|
||||
241
src/cool/modules/chat/components/session.vue
Normal file
241
src/cool/modules/chat/components/session.vue
Normal file
@ -0,0 +1,241 @@
|
||||
<template>
|
||||
<div class="cl-chat-session">
|
||||
<div class="cl-chat-session__search">
|
||||
<el-input
|
||||
v-model="keyWord"
|
||||
placeholder="搜索"
|
||||
prefix-icon="el-icon-search"
|
||||
size="small"
|
||||
clearable
|
||||
@clear="onSearch"
|
||||
@keyup.enter.native="onSearch"
|
||||
></el-input>
|
||||
</div>
|
||||
|
||||
<!-- 会话列表 -->
|
||||
<ul class="cl-chat-session__list scroller1" v-loading="loading">
|
||||
<li
|
||||
class="cl-chat-session__item"
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
:class="{
|
||||
'is-active': session ? item.id == session.id : false
|
||||
}"
|
||||
@click="toDetail(item)"
|
||||
@contextmenu.stop.prevent="openCM($event, item.id, index)"
|
||||
>
|
||||
<!-- 头像 -->
|
||||
<div class="avatar">
|
||||
<el-badge
|
||||
:value="item.serviceUnreadCount"
|
||||
:hidden="item.serviceUnreadCount === 0"
|
||||
:max="99"
|
||||
>
|
||||
<img :src="item.headimgurl" alt="" />
|
||||
</el-badge>
|
||||
</div>
|
||||
|
||||
<!-- 昵称,内容 -->
|
||||
<div class="det">
|
||||
<p class="name">{{ item.nickname }}</p>
|
||||
<p class="content">{{ item.lastMessage }}</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from "vuex";
|
||||
import { parseContent } from "../utils";
|
||||
import eventBus from "../utils/event-bus";
|
||||
import { ContextMenu } from "cl-admin-crud";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
pagination: {
|
||||
page: 1,
|
||||
size: 100,
|
||||
total: 0
|
||||
},
|
||||
keyWord: ""
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters(["sessionList", "session"]),
|
||||
|
||||
list() {
|
||||
return this.sessionList
|
||||
.map(e => {
|
||||
const { _text } = parseContent(e);
|
||||
e.lastMessage = _text;
|
||||
return e;
|
||||
})
|
||||
.sort((a, b) => {
|
||||
return a.updateTime < b.updateTime ? 1 : -1;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
eventBus.$on("session-refresh", this.refresh);
|
||||
this.refresh();
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 右键菜单
|
||||
openCM(e, id, index) {
|
||||
ContextMenu.open(e, {
|
||||
list: [
|
||||
{
|
||||
label: "删除",
|
||||
icon: "el-icon-delete",
|
||||
callback: (_, done) => {
|
||||
this.$service.im.session.delete({
|
||||
ids: id
|
||||
});
|
||||
|
||||
this.list.splice(index, 1);
|
||||
|
||||
if (id == this.session.id) {
|
||||
this.toDetail();
|
||||
}
|
||||
|
||||
done();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
},
|
||||
|
||||
// 刷新列表
|
||||
refresh(params) {
|
||||
this.loading = true;
|
||||
|
||||
this.$service.im.session
|
||||
.page({
|
||||
...this.pagination,
|
||||
keyWord: this.keyWord,
|
||||
params,
|
||||
order: "updateTime",
|
||||
sort: "desc"
|
||||
})
|
||||
.then(res => {
|
||||
this.$store.commit("SET_SESSION_LIST", res.list);
|
||||
this.pagination = res.pagination;
|
||||
})
|
||||
.catch(err => {
|
||||
this.$message.error(err);
|
||||
})
|
||||
.done(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
|
||||
// 搜索关键字
|
||||
onSearch() {
|
||||
this.refresh({ page: 1 });
|
||||
},
|
||||
|
||||
// 会话详情
|
||||
toDetail(item) {
|
||||
if (item) {
|
||||
if (!this.session || this.session.id != item.id) {
|
||||
this.$store.commit("SET_SESSION", item);
|
||||
}
|
||||
} else {
|
||||
this.$store.commit("CLEAR_SESSION");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.cl-chat-session {
|
||||
height: calc(100% - 10px);
|
||||
width: 250px;
|
||||
margin: 5px 0 5px 5px;
|
||||
border-radius: 5px;
|
||||
background-color: #fff;
|
||||
|
||||
&__search {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
&__list {
|
||||
height: calc(100% - 52px);
|
||||
overflow: auto;
|
||||
|
||||
li {
|
||||
display: flex;
|
||||
list-style: none;
|
||||
padding: 10px;
|
||||
border-left: 5px solid #fff;
|
||||
|
||||
.avatar {
|
||||
margin-right: 12px;
|
||||
|
||||
img {
|
||||
display: block;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
border-radius: 3px;
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.el-badge {
|
||||
&__content {
|
||||
height: 14px;
|
||||
line-height: 14px;
|
||||
padding: 0 4px;
|
||||
background-color: #fa5151;
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.det {
|
||||
flex: 1;
|
||||
.name {
|
||||
font-size: 13px;
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
.content {
|
||||
font-size: 12px;
|
||||
margin-top: 5px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.name,
|
||||
.content {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
background-color: #eee;
|
||||
border-color: $color-primary;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #eee;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__empty {
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,4 +1,5 @@
|
||||
import components from "./components";
|
||||
import service from "./service";
|
||||
import store from "./store";
|
||||
|
||||
export default { components, service };
|
||||
export default { components, service, store };
|
||||
|
||||
5
src/cool/modules/chat/store/index.js
Normal file
5
src/cool/modules/chat/store/index.js
Normal file
@ -0,0 +1,5 @@
|
||||
import session from "./session";
|
||||
|
||||
export default {
|
||||
session
|
||||
};
|
||||
44
src/cool/modules/chat/store/session.js
Normal file
44
src/cool/modules/chat/store/session.js
Normal file
@ -0,0 +1,44 @@
|
||||
import eventBus from "../utils/event-bus";
|
||||
|
||||
export default {
|
||||
state: {
|
||||
list: [],
|
||||
current: null
|
||||
},
|
||||
|
||||
getters: {
|
||||
// 当前会话
|
||||
session: state => state.current,
|
||||
// 会话列表
|
||||
sessionList: state => state.list
|
||||
},
|
||||
|
||||
mutations: {
|
||||
// 设置会话信息
|
||||
SET_SESSION(state, data) {
|
||||
state.current = data;
|
||||
state.current.serviceUnreadCount = 0;
|
||||
eventBus.$emit("message-refresh", { page: 1 });
|
||||
},
|
||||
|
||||
// 清空会话信息
|
||||
CLEAR_SESSION(state) {
|
||||
state.session = null;
|
||||
},
|
||||
|
||||
// 更新会话信息
|
||||
UPDATE_SESSION(state, data) {
|
||||
Object.assign(state.current, data);
|
||||
},
|
||||
|
||||
// 设置会话列表
|
||||
SET_SESSION_LIST(state, data) {
|
||||
state.list = data;
|
||||
},
|
||||
|
||||
// 清空会话列表
|
||||
CLEAR_SESSION_LIST(state) {
|
||||
state.list = [];
|
||||
}
|
||||
}
|
||||
};
|
||||
2
src/cool/modules/chat/utils/event-bus.js
Normal file
2
src/cool/modules/chat/utils/event-bus.js
Normal file
@ -0,0 +1,2 @@
|
||||
import Vue from "vue";
|
||||
export default new Vue();
|
||||
@ -48,6 +48,10 @@ Mock.mock("/im/session/unreadCount", "get", options => {
|
||||
};
|
||||
});
|
||||
|
||||
Mock.setup({
|
||||
timeout: "500-1000"
|
||||
});
|
||||
|
||||
Mock.mock("/im/message/page", "post", options => {
|
||||
const data = Mock.mock({
|
||||
"list|20": [
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user