<template>
<div class="customer-servce">
<div class="imServer-wrapper">
<main class="imServer-main">
<im-record
class="item im-record"
:session-list="sessions"
:my-info="myInfo"
:users="users"
@selectedChat="selectedChat"
/>
<im-chat
v-if="JSON.stringify(msgsUser) !== '{}'"
ref="im_chat"
:msgs-user="msgsUser"
class="item im-chat"
>
<common-chat
ref="common_chat"
:chat-info-en="chatInfo"
:msgs-user="msgsUser"
:my-info="myInfo"
:opr-role-name="'server'"
@sendText="sendMsg"
@fileUpload="fileUpload"
@transferChat="transferChat"
/>
</im-chat>
</main>
</div>
</div>
</template>
<script>
// 聊天列表
import imRecord from '@/components/imServer/imRecord.vue'
// 聊天详情
import imChat from '@/components/imServer/imChat.vue'
// 聊天数据框
import commonChat from '@/components/common/common_chat.vue'
// SDK
import SDK from '../../../static/yx/js/NIM_Web_SDK_v4.0.0'
import { getUserId } from '@/utils/auth'
export default {
components: {
imRecord,
imChat,
commonChat
},
data() {
return {
nim: null,
sessions: [],
accountList: null,
users: [],
myInfo: {},
chatInfo: [],
msgsUser: {},
chatType: '3', // 标识客服
sysUserId: getUserId(), // 存储用户id
transfer: false // 是否转客服
}
},
computed: {
},
watch: {},
async created() {
await this.initSDK()
},
mounted() {
},
destroyed() {
},
methods: {
initSDK() {
this.nim = SDK.NIM.getInstance({
// debug: true,
// appKey: '',
// account: '',
// token: ''
// privateConf: {}, // 私有化部署方案所需的配置
onconnect: this.onConnect, // 链接已经建立(登录成功)
onwillreconnect: this.onWillReconnect,
ondisconnect: this.onDisconnect,
onerror: this.onError,
// 用户名片
onmyinfo: this.onMyInfo,
onupdatemyinfo: this.onUpdateMyInfo,
onusers: this.onUsers,
onupdateuser: this.onUpdateUser,
// // 会话
syncSessionUnread: true,
onsessions: this.onSessions,
onupdatesession: this.onUpdateSession,
// 同步完成
onsyncdone: this.onSyncDone
})
},
onConnect(event) {
console.log('已经连接', event)
// if (loginInfo) {
// // 连接上以后更新uid
// commit('updateUserUID', loginInfo)
// }
},
onWillReconnect(event) {
// 此时说明 SDK 已经断开连接, 请开发者在界面上提示用户连接已断开, 而且正在 重新建立连接
console.log(event)
},
onDisconnect(error) {
// 此时说明 SDK 处于断开状态, 开发者此时应该根据错误码提示相应的错误信息, 并且跳转到登录页面
switch (error.code) {
// 账号或者密码错误, 请跳转到登录页面并提示错误
case 302:
console.log('帐号或密码错误', 'login')
break
// 被踢, 请提示错误后跳转到登录页面
case 'kicked':
// const map = {
// PC: '电脑版',
// Web: '网页版',
// Android: '手机版',
// iOS: '手机版',
// WindowsPhone: '手机版'
// }
// // // let str = error.from
// const errorMsg = `你的帐号于${map.PC} || '其他端')}踢出下线,请确定帐号信息安全!`
console.log(error, 'login')
break
default:
break
}
},
onError(event) {
// alert(JSON.stringify(event))
alert('网络连接状态异常')
// location.href = config.loginUrl
},
onSyncDone() {
console.log('数据同步完成')
// 数据同步完成
// dispatch('hideLoading')
// // 说明在聊天列表页
// if (store.state.currSessionId) {
// dispatch('setCurrSession', store.state.currSessionId)
// }
},
onSessions(sessions) {
// 这里使用sdk自带的方法合并会话列表重新赋值
this.sessions = this.nim.mergeSessions(this.sessions, sessions)
// this.usDateSessionsUI() // 更新UI
this.sessions.forEach(item => {
const { lastMsg: { custom }} = item
if (custom) {
const customList = item.lastMsg.custom = typeof (custom) === 'object' ? custom : JSON.parse(custom)
if (customList.sysUserId && customList.sysUserId === this.sysUserId) {
item.hiddleChat = customList.transfer
} else {
item.hiddleChat = false
}
} else {
item.hiddleChat = false
}
})
console.log(11111111, this.sessions)
},
// 更新会话
onUpdateSession(session) {
this.sessions = this.nim.mergeSessions(this.sessions, session)
this.sessions.forEach(item => {
const { lastMsg: { custom }} = item
if (custom) {
const customList = item.lastMsg.custom = typeof (custom) === 'object' ? custom : JSON.parse(custom)
if (customList.sysUserId && customList.sysUserId === this.sysUserId) {
item.hiddleChat = customList.transfer
} else {
item.hiddleChat = false
}
} else {
item.hiddleChat = false
}
})
console.log(2222222, this.sessions)
// this.usDateSessionsUI()// 更新UI
},
// 获取历史记录
getHistoryMsgs(e) {
this.nim.getHistoryMsgs({
scene: 'p2p',
to: e,
asc: true,
done: getHistoryMsgsDone
})
const that = this
function getHistoryMsgsDone(error, obj) {
if (!error) {
console.log('获取历史记录', obj.msgs)
const msgs = obj.msgs
that.chatInfo = msgs
that.$refs.common_chat.goEnd()
}
}
},
// 名片
onMyInfo(user) {
console.log('收到我的名片', user)
this.myInfo = user
},
onUpdateMyInfo(user) {
console.log('我的名片更新了', user)
this.myInfo = SDK.NIM.util.merge(this.myInfo, user)
},
onUsers(users) {
console.log('收到用户名片列表', users)
this.users = this.nim.mergeUsers(this.users, users)
console.log(88888, this.users)
},
onUpdateUser(user) {
console.log('用户名片更新了', user)
this.users = this.nim.mergeUsers(this.users, user)
},
// 获取用户信息
getUserInfo() {
console.log(3333)
const accounts = this.sessions.map((item) => {
if (item.lastMsg) {
if (item.lastMsg.flow === 'in') {
return item.lastMsg.from
} else {
return item.lastMsg.to
}
}
})
console.log(7777, accounts)
this.nim.getUsers({
accounts: accounts,
done: (error, users) => {
console.log(888)
if (!error) {
this.accountList = users.map((it, idx) => {
for (let i = 0; i < this.sessions.length; i++) {
if (this.sessions[i].lastMsg.flow === 'in') {
if (it.account === this.sessions[i].lastMsg.from) {
console.log(33333, it)
}
} else {
if (it.account === this.sessions[i].lastMsg.to) {
console.log(444444, it)
}
}
}
return it
})
}
}
})
},
// 选中了会话
selectedChat(e) {
console.log('选中了会话', e)
this.msgsUser = e
this.getHistoryMsgs(e.to)
},
// 转移客服
transferChat(e) {
this.sendMsg(e, { transfer: true })
},
/**
* 发送消息
* @param {Object} rs 回调对象
* @Chat 判断是否正在聊天中
*/
sendMsg(rs, transfer) {
const transferInfo = (transfer && transfer['transfer']) || this.transfer
console.log('发送消息', this.msgsUser.to)
this.nim.sendText({
scene: 'p2p',
to: this.msgsUser.to,
text: rs,
custom: JSON.stringify({
chatType: this.chatType,
sysUserId: this.sysUserId,
transfer: transferInfo,
customerAccid: this.msgsUser.to,
customerId: this.msgsUser.lastMsg['customerId']
}),
done: sendMsgDone
})
const self = this
function sendMsgDone(error, msg) {
if (!error) {
self.chatInfo.push(msg)
self.$refs.common_chat.goEnd()
}
}
},
// 预览文件
previewFile() {
const self = this
self.nim.previewFile({
type: 'image',
fileInput: 'common_chat_opr_fileUpload',
done: function(error, file) {
if (!error) {
console.log(file)
}
}
})
},
// 上传图片
fileUpload() {
const self = this
self.nim.sendFile({
scene: 'p2p',
to: self.msgsUser.to,
type: 'image',
custom: JSON.stringify({
chatType: self.chatType,
sysUserId: self.sysUserId,
transfer: self.transfer,
customerAccid: self.msgsUser.to,
customerId: this.msgsUser.lastMsg['customerId']
}),
fileInput: 'common_chat_opr_fileUpload',
done: sendMsgDone
})
function sendMsgDone(error, msg) {
if (!error) {
self.chatInfo.push(msg)
self.$refs.common_chat.goEnd()
}
}
}
}
}
</script>
<style lang="scss">
.customer-servce {
margin: 10px 20px 40px 20px;
background-color: #FFFFFF;
height: 100vh;
.imServer-wrapper {
box-shadow: 0 0 3px 3px #eee;
background: #FFFFFF;
width: 100%;
height: 100%;
overflow: hidden;
.imServer-main {
height: 100%;
max-width: 100%;
position: relative;
& > .item {
float: left;
border-right: 1px solid #e6e6e6;
height: 100%;
}
& > .im-record {
width: 280px;
}
& > .im-chat {
width: calc(100% - 280px);
}
}
}
}
</style>
// 聊天列表
<!-- 会话记录 -->
<template>
<div class="imRecord-wrapper">
<header class="header">
<div class="kf-info-wrapper">
<img class="kf-avatar" :src="myInfo['avatar'] || ''">
<span class="kf-name position-h-v-mid">
{{ myInfo.nick }}
<span>{{ myInfo.sign }}</span>
</span>
</div>
</header>
<main class="main">
<div v-if="sessionList.length > 0" class="item-list">
<div
v-for="(tmpEn, index) in sessionList"
:key="index"
class="item"
:class="{ active: tmpEn.id === activeId}"
@click="selectChat(tmpEn, users[index])"
>
<div class="followicon-wrapper">
<i class="iconfont icon-zhidingwujiaoxing position-h-v-mid" :class="{ active: tmpEn.id === activeId}" @click.stop="toggleFollowIcon(tmpEn)" />
</div>
<!-- 客户端头像 -->
<div class="platicon-wrapper">
<div class="listImg header-img">
<img :key="users[index]['avatar']" v-lazy="users[index]['avatar']" alt="">
</div>
</div>
<div class="info-wrapper">
<p class="first-p">
<span class="name">
{{ users[index].nick }}
<span v-if="tmpEn.hiddleChat" style="color:#F56C6C; font-size: 12px">转</span>
</span>
<span class="lastMsgTime">{{ getLastMsgTimeStr(tmpEn.lastMsg.time) }}</span>
</p>
<p class="second-p">
<span class="lastMsgContent">{{ tmpEn.lastMsg.text }}</span>
<el-badge v-show="tmpEn.unread > 0" class="newMsgCount" :value="tmpEn.unread" :max="99" />
</p>
</div>
</div>
</div>
<div v-else class="empty-wrapper">
<div class="content">
<i class="iconfont fa fa-commenting-o" />
<p class="title">当前没有会话</p>
</div>
</div>
</main>
</div>
</template>
<script>
import { formatTime } from '@/utils'
export default {
name: 'ImRecord',
props: {
sessionList: {
type: Array,
default() {
return []
}
},
myInfo: {
type: Object,
default() {
return {}
}
},
users: {
type: Array,
default() {
return []
}
}
},
data() {
return {
session: [],
activeId: ''
}
},
watch: {
},
created() {
},
methods: {
/**
* 选中当前列表的chat
* @param {Object} en call实体类
*/
selectChat(en, users) {
this.activeId = en.id
// this.$store.dispatch('selectChat', { clientChatId: en.clientChatId })
this.$emit('selectedChat', { ...en, ...users }) // 事件上传
},
/**
* 设置关注
*/
toggleFollowIcon: function(en) {
en.isFollow = !en.isFollow
// 排序
this.$store.imServerStore.commit('sortCurrentChatEnlist', {})
},
/**
* 获取背景class
* @param {string} clientChatName 姓名
*/
getBgClass: function(clientChatName) {
const rs = clientChatName.charCodeAt(0) % 5
return 'bg' + rs
},
/**
* 返回chat对象的最后一次消息时间
* @param {String|Date} sValue 传入的时间字符串
*/
getLastMsgTimeStr: function(sValue) {
if (sValue === null) {
return ''
}
const rs = formatTime(sValue)
return rs
}
}
}
</script>
<style lang="scss">
.imRecord-wrapper {
p {
margin: 0;
}
width: 100%;
height: 550px;
overflow: hidden;
border: 0;
& > .header {
background: url('../../assets/systemManage/LoginBg.png') no-repeat 100% center scroll;
overflow: hidden;
display: flex;
align-items: center;
height: 60px;
border-bottom: 1px solid #e6e6e6;
.kf-info-wrapper {
display: flex;
width: 100%;
height: 50px;
padding: 0 20px;
.kf-avatar {
align-self: center;
width: 45px;
height: 45px;
border-radius: 50%;
}
.kf-name {
margin-left: 10px;
align-self: center;
font-size: 16px;
span {
display: inline-block;
font-size: 12px;
color: #eaf4fb;
}
}
}
.client-info-wrapper {
p:first-child {
margin-bottom: 5px;
}
.fa {
margin-right: 10px;
}
}
}
& > .main {
height: calc(100% - 50px);
position: relative;
.item-list {
height: calc(100% - 46px);
overflow-y: auto;
.item {
position: relative;
height: 63px;
padding: 0px;
border-bottom: 1px solid #e6e6e6;
cursor: pointer;
&.active,
&:hover {
background-color: #eee;
.info-wrapper .first-p .name,
.info-wrapper .second-p .lastMsgContent,
.info-wrapper .first-p .lastMsgTime {
color: #8d8d8d;
}
.iconfont {
display: initial !important;
}
}
.followicon-wrapper {
position: relative;
float: left;
width: 25px;
height: 100%;
.iconfont {
display: none;
font-size: 12px;
color: #eaf4fb;
&.active {
display: initial;
color: #f9ce1d;
}
}
}
.platicon-wrapper {
display: flex;
align-items: center;
float: left;
width: 50px;
height: 100%;
.header-img {
width: 40px;
height: 40px;
/*&.bg0 {*/
/* background-color: #ef7777;*/
/*}*/
/*&.bg1 {*/
/* background-color: #00bcd4;*/
/*}*/
/*&.bg2 {*/
/* background-color: #009688;*/
/*}*/
/*&.bg3 {*/
/* background-color: #ffc107;*/
/*}*/
/*&.bg4 {*/
/* background-color: #ff5722;*/
/*}*/
}
}
.info-wrapper {
position: relative;
float: left;
width: 185px;
padding-top: 5px;
padding-left: 5px;
.first-p {
clear: both;
padding-top: 11px;
.name {
display: inline-block;
float: left;
width: 50%;
font-size: 14px;
color: #3e3e3e;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
text-align: left;
font-weight: bolder;
}
.lastMsgTime {
float: right;
font-size: 12px;
color: #8d8d8d;
}
}
.second-p {
clear: both;
padding-top: 5px;
line-height: 1.2;
.lastMsgContent {
display: inline-block;
width: 150px;
margin-top: 3px;
font-size: 12px;
color: #8d8d8d;
white-space: nowrap;
text-overflow: ellipsis;
text-align: left;
overflow: hidden;
}
.newMsgCount {
position: relative;
top: 4px;
float: right;
.el-badge__content {
border: 1px solid #ffffff00;
}
}
}
}
}
}
.empty-wrapper {
font-size: 16px;
color: #3e3e3e;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
.content {
width: 170px;
height: 200px;
margin: 0px auto;
text-align: center;
color: #867c7c;
.iconfont {
font-size: 90px;
}
.title {
margin-top: 25px;
}
}
}
}
}
</style>
聊天内容
<!-- 聊天记录 -->
<template>
<div class="imChat-wrapper">
<!-- 头部 -->
<header class="imChat-header">
<span class="name">
{{ msgsUser.nick }}
<span style=" color: #8d8d8d; font-size: 15px;">{{ msgsUser.sign }}</span>
</span>
</header>
<main class="imChat-main">
<slot />
</main>
</div>
</template>
<script>
export default {
components: {
},
props: {
msgsUser: {
type: Object,
default() {
return []
}
}
}
}
</script>
<style lang="scss">
.imChat-wrapper {
.imChat-header {
display: flex;
align-items: center;
width: 100%;
height: 60px;
// background: #eee;
padding-left: 10px;
border-bottom: 1px solid #e6e6e6;
font-size: 16px;
span {
margin-right: 20px;
}
.on-line {
color: #70ed3a;
}
.off-line {
color: #bbbbbb;
}
}
.imChat-main {
height: calc(100% - 50px);
}
}
</style>
聊天框
<!-- 聊天记录 -->
<template>
<div class="common_chat-wrapper">
<div class="common_chat-inner">
<!-- 聊天记录 -->
<div id="common_chat_main" ref="common_chat_main" class="common_chat-main">
<div class="common_chat-main-content">
<div class="inner">
<div v-for="(item ,index) in chatInfoEn" :key="index">
<!-- 客户、客服 -->
<div class="item" :class="{ sender: item.flow === 'out', receiver: item.flow === 'in' }">
<!--时间-->
<div
v-if="InfoToTime(item.time, chatInfoEn[index - 1]) !== ''"
style="text-align: center; clear: both; padding: 10px 0; color: rgb(131, 129, 135)"
>
<span> -- {{ InfoToTime(item.time, chatInfoEn[index - 1]) }} --</span>
</div>
<div class="info-wrapper">
<!-- 头像 -->
<div class="avatar-wrapper">
<img :key="item.flow === 'out' ? myInfo.avatar : item.avatar" v-lazy="item.flow === 'out' ? myInfo.avatar : item.avatar" class="kf-img">
</div>
<!-- 1)文本类型 -->
<div v-if="item.type ==='text'" class="item-content common_chat_emoji-wrapper-global">
<p class="text">{{ item.text }}</p>
</div>
<!-- 2)图片类型 -->
<div v-else-if="item.type ==='image'" class="item-content">
<img class="img" :src="item.file.url" @click="imgViewDialog_show(item.file)">
</div>
<!-- 3)文件类型 -->
<div v-else-if="item.type ==='file'" class="item-content">
<div class="file">
<i class="file-icon iconfont fa fa-file" />
<div class="file-info">
<p class="file-name">{{ getFileName(item.fileName) }}</p>
<div class="file-opr">
<div v-show="item.state === 'success'">
<a class="file-download" :href="item.fileUrl" target="_blank" :download="item.fileUrl">下载</a>
</div>
</div>
</div>
</div>
</div>
<!-- <!– 4)文本类型 –>-->
<!-- <div v-if="item.type ==='transformServer'" class="item-content common_chat_emoji-wrapper-global">-->
<!-- <p class="text">-->
<!-- 当前没有配置机器人,-->
<!-- <el-button type="text" @click="chatCallback('transformServer')">转接客服</el-button>-->
<!-- </p>-->
<!-- </div>-->
</div>
</div>
</div>
</div>
</div>
</div>
<div class="common_chat-footer">
<div>
<!-- 表情、文件选择等操作 -->
<div class="opr-wrapper">
<common-chat-emoji ref="qqemoji" class="item" @activeEmoji="qqemoji_selectFace" />
<a class="item" href="javascript:void(0)" @click="fileUpload_click('file')">
<i class="el-icon-picture-outline" style="font-size: 20px; color: #aaa" />
</a>
<form method="post" enctype="multipart/form-data">
<input
id="common_chat_opr_fileUpload"
type="file"
name="uploadFile"
style="display:none;position:absolute;left:0;top:0;width:0%;height:0%;opacity:0;"
accept="image/png,image/gif,image/jpg,image/jpeg"
>
</form>
</div>
<!-- 聊天输入框 -->
<div class="input-wrapper">
<el-input
v-model="inputContent"
type="textarea"
placeholder="请输入内容"
maxlength="500"
@keydown.native="listenInput($event)"
/>
</div>
<!-- 发送按钮 -->
<el-button
v-if="!msgsUser.hiddleChat"
size="small"
class="send-btn"
:disabled="inputContent.length === 0"
@click="sendText"
>
发送
</el-button>
<el-button
v-if="!msgsUser.hiddleChat"
size="small"
class="send-btn"
@click="transferChat"
>
转移客服
</el-button>
</div>
<!-- 离线 -->
<div v-show="msgsUser.hiddleChat" class="off-wrapper">
<span class="content">会话已经结束</span>
</div>
</div>
</div>
<!-- 图片查看dialog -->
<el-dialog title :visible.sync="imgViewDialogVisible" class="imgView-dialog" :modal="false">
<div class="header">
<i class="iconfont fa fa-remove" @click="imgViewDialogVisible = !imgViewDialogVisible" />
</div>
<div class="main">
<img class="img" :src="imgViewDialog_imgSrc.url" :width="imgViewDialog_imgSrc.width" :height="imgViewDialog_imgSrc.height">
</div>
</el-dialog>
</div>
</template>
<script>
import common_chat_emoji from './common_chat_emoji.vue'
import { InfoTime, parseTime } from '@/utils'
export default {
components: {
commonChatEmoji: common_chat_emoji
},
props: {
chatInfoEn: {
type: Array,
default() {
return []
}
},
myInfo: {
type: Object,
default() {
return {}
}
},
msgsUser: {
type: Object,
default() {
return {}
}
},
oprRoleName: {
required: true,
type: String,
default: ''
} // 操作者名称;e.g. server:服务端、client:客服端
},
data() {
return {
inputContent: '',
transferInfo: '将为您转接人工客服,请稍等',
imgViewDialogVisible: false, // 图片查看dialog的显示
imgViewDialog_imgSrc: {} // 图片查看dialog的图片地址
}
},
computed: {},
watch: {
chatInfoEn() {
this.inputContent = ''
}
},
mounted() {
},
methods: {
// 转移客服
transferChat() {
this.$emit('transferChat', this.transferInfo)
},
// 键盘事件
listenInput(event) {
if (event.keyCode === 13) {
event.preventDefault()
this.sendText()
}
},
InfoToTime(time, lastTime) {
if (lastTime) {
const Info = InfoTime(time, lastTime.time)
return Info
} else {
return parseTime(time, '{m}-{d} {h}:{i}')
}
},
// 发送文本
sendText() {
this.$emit('sendText', this.inputContent)
},
// 文件上传_点击
fileUpload_click(fileType) {
document.getElementById('common_chat_opr_fileUpload').onchange = this.fileUpload_change
document.getElementById('common_chat_opr_fileUpload').click()
},
/**
* 文件上传_选中文件
*/
fileUpload_change(e) {
this.$emit('fileUpload', e)
},
// qqemoji选中表情
qqemoji_selectFace(rs) {
this.inputContent += rs
},
/**
* 转换文件名,若文件名称超过9个字符,将进行截取处理
* @param {String} fileName 文件名称
*/
getFileName: function(fileName) {
if (!fileName) {
return
}
let name = fileName.substring(0, fileName.lastIndexOf('.'))
const extend = fileName.substring(fileName.lastIndexOf('.') + 1)
if (name.length > 9) {
name = name.substring(0, 3) + '...' + name.substring(name.length - 3)
}
return name + '.' + extend
},
// 图片查看dialog_显示
imgViewDialog_show(item) {
this.imgViewDialogVisible = true
this.imgViewDialog_imgSrc = item
},
// 聊天记录滚动到底部
goEnd() {
this.$nextTick(() => {
setTimeout(() => {
this.$refs.common_chat_main.scrollTop = this.$refs.common_chat_main.scrollHeight
}, 100)
})
}
}
}
</script>
<style lang="scss">
.common_chat-wrapper {
/*定义滚动条高宽及背景 高宽分别对应横竖滚动条的尺寸*/
::-webkit-scrollbar
{
width: 8px;
height: 16px;
background-color: #ffffff;
}
/*定义滚动条轨道 内阴影+圆角*/
::-webkit-scrollbar-track
{
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.2);
border-radius: 10px;
background-color: #ffffff;
}
/*定义滑块 内阴影+圆角*/
::-webkit-scrollbar-thumb
{
border-radius: 10px;
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3);
background-color: #eee;
}
p {
margin: 0;
}
width: 100%;
height: 100%;
overflow: hidden;
position: relative;
font-size: 12px;
float: left;
border: 0;
.common_chat-inner {
width: 100%;
height: 100%;
.common_chat-main {
position: relative;
height: calc(100% - 150px);
overflow-y: auto;
overflow-x: hidden;
.common_chat-main-header {
padding-top: 14px;
text-align: center;
.el-button {
padding: 0;
font-size: 12px;
color: #8d8d8d;
}
}
.common_chat-main-content {
position: absolute;
width: 100%;
height: 100%;
& > .inner {
padding-bottom: 20px;
.item {
clear: both;
overflow: hidden;
}
.sys {
color: #b0b0b0;
font-size: 12px;
text-align: center;
.text-content {
padding-top: 20px;
}
.myd-content {
.desc {
margin-top: 18px;
}
.text {
color: #3e3e3e;
margin-top: 12px;
}
.remark {
margin-top: 10px;
}
}
}
.receiver,
.sender {
margin-top: 18px;
font-size: 12px;
.avatar-wrapper {
float: left;
.kf-img {
width: 40px;
height: 40px;
}
}
.info-wrapper {
position: relative;
text-align: left;
font-size: 12px;
.item-content {
position: relative;
max-width: 330px;
color: #3e3e3e;
font-size: 13px;
border-radius: 3px;
.text {
line-height: 1.8;
white-space: normal;
word-wrap: break-word;
word-break: break-all;
padding: 10px 12px;
}
.qqemoji {
width: 24px;
height: 24px;
}
.img {
max-width: 320px;
max-height: 240px;
white-space: normal;
word-wrap: break-word;
word-break: break-all;
padding: 5px;
cursor: pointer;
}
.file {
width: 220px;
padding: 10px 8px;
margin: 3px;
overflow: hidden;
background: #fff;
border-radius: 5px;
.el-button {
padding: 0px;
font-size: 12px;
}
.file-info {
float: left;
padding: 0px 8px;
.file-name {
width: 160px;
display: inline-block;
color: #333333;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
line-height: 1.3;
}
}
.file-opr {
margin-top: 8px;
}
.file-icon {
float: left;
color: #663399;
font-size: 40px;
}
.file-download {
color: #00a8d7;
cursor: pointer;
text-decoration: blink;
}
}
.preInput {
position: relative;
color: #8d8d8d;
img {
height: 15px;
position: relative;
top: 3px;
}
}
.issueList {
width: 250px;
padding: 10px;
.title {
position: relative;
.content {
position: absolute;
margin-top: -1px;
margin-left: 6px;
}
}
.el-collapse-item__wrap {
background: transparent;
}
.el-collapse {
border: 0px;
margin-top: 8px;
margin-bottom: -8px;
.el-collapse-item__header {
font-size: 13px;
background: transparent;
color: #f7455d;
padding-left: 5px;
}
.el-collapse-item__wrap {
.el-collapse-item__content {
font-size: 12px;
color: #3e3e3e;
padding-left: 5px;
}
}
}
}
.issueExtend {
width: 250px;
padding: 10px 10px 0px;
.main {
border-top: 1px solid #eeeff0;
margin-top: 10px;
padding-top: 10px;
p {
margin-bottom: 5px;
}
.el-button {
font-size: 12px;
color: #f7455d;
}
}
}
.issueResult {
width: 250px;
.main {
padding: 10px;
}
.footer {
border-top: 1px solid #eeeff0;
height: 30px;
.btn {
width: 60px;
margin: 0px 30px;
padding: 6px 0px;
display: inline-block;
text-align: center;
font-size: 10px;
color: #8d8d8d;
cursor: pointer;
position: relative;
&:first-child::after {
top: 4px;
right: -30px;
width: 1px;
height: 80%;
content: '';
position: absolute;
background-color: #eeeff0;
z-index: 0;
}
}
.iconfont {
font-size: 10px;
margin-right: 5px;
}
}
}
}
}
}
.item.receiver {
margin-left: 5px;
.avatar-wrapper {
margin-right: 15px;
}
.info-wrapper {
.item-content {
float: left;
color: #000000;
background-color: #f9fbfc;
border: 1px solid #ccc;
&::before {
position: absolute;
top: -1px;
left: -10px;
width: 0px;
height: 0px;
content: '';
border-top: 0px;
border-right: 10px solid #ccc;
border-bottom: 5px solid transparent;
border-left: 0px;
}
}
}
}
.item.sender {
margin-right: 5px;
.avatar-wrapper {
float: right;
margin-left: 15px;
}
.info-wrapper {
float: right;
.item-content {
float: right;
background: #eee;
border: 1px solid #eee;
&::before {
position: absolute;
top: -1px;
right: -10px;
width: 0;
height: 0;
content: '';
border-top: 0;
border-right: 0;
border-bottom: 5px solid transparent;
border-left: 10px solid #eee;
}
}
}
}
}
}
}
.common_chat-footer {
position: relative;
width: 100%;
border-top: 1px solid #ccc;
.opr-wrapper {
height: 20px;
padding: 10px;
text-align: left;
& > .item {
margin-right: 12px;
float: left;
font-weight: normal;
text-decoration: blink;
& > .iconfont {
color: #aaa;
font-size: 20px;
}
}
}
.input-wrapper {
position: relative;
padding: 2px 0px 0px 10px;
.inputContent {
width: 99%;
padding: 2px;
height: 85px;
resize: none;
overflow: auto;
line-height: 1.5;
outline: 0px solid transparent;
}
.shortcutPopover-wrapper {
position: absolute;
top: 30px;
left: 10px;
width: 440px;
max-height: 80px;
padding: 4px;
font-size: 12px;
overflow-y: auto;
border: 1px solid #9b9aab;
border-radius: 3px;
background-color: #fff;
cursor: pointer;
p {
padding: 4px;
&.selected {
background-color: #ded1cc;
}
.key {
display: inline-block;
width: 50px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.content {
display: inline-block;
width: 350px;
margin-left: 10px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.highlight {
color: #00a8d7;
}
}
}
.tips {
position: absolute;
top: 7px;
left: 20px;
width: auto;
color: #8d8d8d;
}
}
.send-btn {
float: right;
margin-right: 16px;
&.off,
&.end {
background-color: #ccc;
border-color: #ccc;
}
}
.off-wrapper {
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
background-color: rgba(255, 255, 255, 0.6);
font-size: 14px;
.content {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
}
}
}
.imgView-dialog {
background: #00000080;
height: 100%;
.el-dialog {
max-width: 75%;
position: relative;
background: transparent;
box-shadow: none;
.el-dialog__header {
display: none;
}
.el-dialog__body {
padding: 0px;
text-align: center;
position: relative;
.header {
text-align: right;
position: relative;
height: 0px;
.fa-remove {
font-size: 32px;
color: white;
position: relative;
right: -50px;
top: -30px;
cursor: pointer;
}
}
.main {
.img {
max-width: 100%;
max-height: 100%;
height: 100%;
}
}
}
}
}
</style>
<style lang="scss">
.common_chat-wrapper {
.el-textarea__inner {
border: none;
height: 100%;
resize: none;
}
.common_chat-footer {
.el-button {
background-color: #eee;
border-color: #eee;
color: rgb(131, 129, 135);
border-radius: 0;
}
}
}
</style>
表情
export const emojiMap = new Map([
['grinning', '😀'],
['grin', '😁'],
['joy', '😂'],
['smiley', '😃'],
['smile', '😄'],
['sweat_smile', '😅'],
['laughing', '😆'],
['satisfied', '😆'],
['innocent', '😇'],
['smiling_imp', '😈'],
['wink', '😉'],
['blush', '😊'],
['yum', '😋'],
['relieved', '😌'],
['heart_eyes', '😍'],
['sunglasses', '😎'],
['smirk', '😏'],
['neutral_face', '😐'],
['expressionless', '😑'],
['unamused', '😒'],
['sweat', '😓'],
['pensive', '😔'],
['confused', '😕'],
['confounded', '😖'],
['kissing', '😗'],
['kissing_heart', '😘'],
['kissing_smiling_eyes', '😙'],
['kissing_closed_eyes', '😚'],
['stuck_out_tongue', '😛'],
['stuck_out_tongue_winking_eye', '😜'],
['stuck_out_tongue_closed_eyes', '😝'],
['disappointed', '😞'],
['worried', '😟'],
['angry', '😠'],
['rage', '😡'],
['cry', '😢'],
['persevere', '😣'],
['triumph', '😤'],
['disappointed_relieved', '😥'],
['frowning', '😦'],
['anguished', '😧'],
['fearful', '😨'],
['weary', '😩'],
['sleepy', '😪'],
['tired_face', '😫'],
['grimacing', '😬'],
['sob', '😭'],
['open_mouth', '😮'],
['hushed', '😯'],
['cold_sweat', '😰'],
['scream', '😱'],
['astonished', '😲'],
['flushed', '😳'],
['sleeping', '😴'],
['dizzy_face', '😵'],
['no_mouth', '😶'],
['mask', '😷'],
['smile_cat', '😸'],
['joy_cat', '😹'],
['smiley_cat', '😺'],
['heart_eyes_cat', '😻'],
['smirk_cat', '😼'],
['kissing_cat', '😽'],
['pouting_cat', '😾'],
['crying_cat_face', '😿'],
['scream_cat', '🙀'],
['no_good', '🙅'],
['ok_woman', '🙆'],
['bow', '🙇'],
['see_no_evil', '🙈'],
['hear_no_evil', '🙉'],
['speak_no_evil', '🙊'],
['raising_hand', '🙋'],
['raised_hands', '🙌'],
['person_frowning', '🙍'],
['person_with_pouting_face', '🙎'],
['pray', '🙏']
])