这是基于vue-vben-admin 模板适用于abp Vnext的前端管理项目
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.
 
 
 
 
 
 

762 lines
20 KiB

<template>
<div>
<div>
<lemon-imui
ref="IMUI"
:user="currentUser"
@send="handleSendMessage"
@pull-messages="onPullMessages"
@change-menu="onChangeMenu"
@change-contact="onChangeContract"
@message-click="handleMessageClick"
@menu-avatar-click="handleMenuAvatarClick"
/>
</div>
</div>
</template>
<script lang="ts">
import EventBusMiXin from '@/mixins/EventBusMiXin'
import Component, { mixins } from 'vue-class-component'
import AddFriend from './components/AddFriend.vue'
import { abpPagerFormat } from '@/utils'
import { UserModule } from '@/store/modules/user'
import ImApiService, {
MyFrientGetAll,
UserMessageGetByPaged,
GetUserLastMessage,
UserFriend,
ChatMessage,
AddUserFriend,
MessageType
} from '@/api/instant-message'
import { HubConnection, HubConnectionBuilder, HubConnectionState } from '@microsoft/signalr'
class MyContract {
id = ''
displayName = ''
avatar = 'http://upload.qqbodys.com/allimg/1710/1035512943-0.jpg'
type = ''
index = 'A'
unread = 0
lastSendTime = 1345209465000
lastContent = ''
readMsgPage = 1
}
class ChatMenu {
name = ''
title = ''
unread = 0
click?: Function
render?: Function
renderContainer?: Function
isBottom = false
constructor(
name: string,
title: string,
unread = 0,
isBottom = false
) {
this.name = name
this.title = title
this.unread = unread
this.isBottom = isBottom
}
}
class FromUser {
id = ''
displayName = ''
avatar = ''
}
class Message {
id = ''
status = ''
type = 'text'
sendTime = 0
content = ''
fileSize = 0
fileName = ''
toContactId = ''
fromUser = new FromUser()
public fromChatMessage(chatMessage: ChatMessage) {
this.id = chatMessage.messageId
this.content = chatMessage.content
this.fromUser.id = chatMessage.formUserId
this.fromUser.displayName = chatMessage.formUserName
this.fromUser.avatar = 'http://upload.qqbodys.com/allimg/1710/1035512943-0.jpg'
this.toContactId = chatMessage.toUserId
this.type = ChatMessage.getType(chatMessage.messageType)
this.sendTime = new Date(chatMessage.sendTime).getTime()
}
public static tryParseToChatMessage(message: Message) {
const chatMessage = new ChatMessage()
chatMessage.formUserId = message.fromUser.id
chatMessage.formUserName = message.fromUser.displayName
chatMessage.toUserId = message.toContactId
chatMessage.content = message.content
chatMessage.sendTime = new Date(message.sendTime)
chatMessage.messageType = Message.getChatMessageType(message)
return chatMessage
}
public static getChatMessageType(message: Message) {
switch (message.type) {
case 'text' :
return MessageType.Text
case 'video' :
return MessageType.Video
case 'image' :
return MessageType.Image
case 'voice' :
return MessageType.Voice
case 'file' :
return MessageType.File
default :
return MessageType.Text
}
}
}
@Component({
name: 'LemonIMUI',
components: {
AddFriend
}
})
export default class extends mixins(EventBusMiXin) {
private showDialog = false
private myFriendCount = 0
private myFriends = new Array<UserFriend>()
private connection!: HubConnection
private chatMenus = new Array<ChatMenu>()
private getMyFriendFilter = new MyFrientGetAll()
private getChatMessageFilter = new UserMessageGetByPaged()
private showAddFriendDialog = false
get currentUser() {
return {
id: UserModule.id,
displayName: UserModule.userName,
avatar: ''
}
}
mounted() {
this.unSubscribeAll()
this.subscribe('onShowImDialog', this.onShowImDialog)
this.subscribe('onReceivedChatMessage', this.onReceivedChatMessage)
this.subscribe('LINGYUN.Abp.Messages.IM.FriendValidation', this.onNewFriendValidation)
this.handleInitDefaultMenus()
this.handleStartConnection()
}
destroyed() {
this.unSubscribe('onShowImDialog')
}
private handleInitIMUI() {
const imui = this.$refs.IMUI as any
ImApiService
.getMyAllFriends(this.getMyFriendFilter)
.then(res => {
this.myFriends = res.items
this.myFriendCount = res.items.length
this.handleInitContracts(imui)
})
.finally(() => {
imui.initMenus(this.chatMenus)
this.handleInitEmoji(imui)
this.handleInitLastContractMessages(imui)
})
}
private handleInitEmoji(imui: any) {
imui.initEmoji([
{
label: '表情',
children: [
{
name: '1f600',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f600.png'
},
{
name: '1f62c',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f62c.png'
},
{
name: '1f601',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f601.png'
},
{
name: '1f602',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f602.png'
},
{
name: '1f923',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f923.png'
},
{
name: '1f973',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f973.png'
},
{
name: '1f603',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f603.png'
},
{
name: '1f604',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f604.png'
},
{
name: '1f605',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f605.png'
},
{
name: '1f606',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f606.png'
},
{
name: '1f607',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f607.png'
},
{
name: '1f609',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f609.png'
},
{
name: '1f60a',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f60a.png'
},
{
name: '1f642',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f642.png'
},
{
name: '1f643',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f643.png'
},
{
name: '1263a',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/263a.png'
},
{
name: '1f60b',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f60b.png'
},
{
name: '1f60c',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f60c.png'
},
{
name: '1f60d',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f60d.png'
},
{
name: '1f970',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f970.png'
},
{
name: '1f618',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f618.png'
},
{
name: '1f617',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f617.png'
},
{
name: '1f619',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f619.png'
},
{
name: '1f61a',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f61a.png'
},
{
name: '1f61c',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f61c.png'
},
{
name: '1f92a',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f92a.png'
},
{
name: '1f928',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f928.png'
},
{
name: '1f9d0',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f9d0.png'
},
{
name: '1f61d',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f61d.png'
},
{
name: '1f61b',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f61b.png'
},
{
name: '1f911',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f911.png'
},
{
name: '1f913',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f913.png'
},
{
name: '1f60e',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f60e.png'
},
{
name: '1f929',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f929.png'
},
{
name: '1f921',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f921.png'
},
{
name: '1f920',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f920.png'
},
{
name: '1f917',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f917.png'
},
{
name: '1f60f',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f60f.png'
},
{
name: '1f636',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f636.png'
},
{
name: '1f610',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f610.png'
},
{
name: '1f611',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f611.png'
},
{
name: '1f612',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f612.png'
},
{
name: '1f644',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f644.png'
},
{
name: '1f914',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f914.png'
},
{
name: '1f925',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f925.png'
},
{
name: '1f92d',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f92d.png'
},
{
name: '1f92b',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f92b.png'
},
{
name: '1f92c',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f92c.png'
},
{
name: '1f92f',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f92f.png'
},
{
name: '1f633',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f633.png'
},
{
name: '1f61e',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f61e.png'
},
{
name: '1f61f',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f61f.png'
},
{
name: '1f620',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f620.png'
},
{
name: '1f621',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f621.png'
}
]
}
])
}
private handleInitContracts(imui: any) {
const myContracts = new Array<MyContract>()
this.myFriends
.forEach(friend => {
const myContract = new MyContract()
myContract.id = friend.friendId
myContract.displayName = friend.remarkName ?? friend.userName
myContract.unread = 0
myContract.type = 'many'
myContracts.push(myContract)
})
imui.initContacts(myContracts)
}
private handleInitLastContractMessages(imui: any) {
const filter = new GetUserLastMessage()
ImApiService
.getMyLastMessages(filter)
.then(res => {
res.items
.sort((now, next) => { // 如果一条消息是与同一个用户的发送与接收,会被分为两条消息返回,所以需要按照发送时间升序排序,历史消息才会准确
return next.sendTime < now.sendTime ? 1 : -1
})
.forEach(msg => {
const imuiMsg = new Message()
imuiMsg.fromChatMessage(msg)
let contractId = msg.formUserId
if (msg.formUserId === this.currentUser.id) {
contractId = msg.toUserId
}
imui.updateContact(contractId, {
lastSendTime: imuiMsg.sendTime,
lastContent: imui.lastContentRender(imuiMsg)
})
})
})
}
private handleInitDefaultMenus() {
const lastMsgMenu = new ChatMenu('lastMessages', '历史消息')
const contractsMenu = new ChatMenu('contacts', '联系人')
const addFriendMenu = new ChatMenu('addFriends', '添加朋友')
addFriendMenu.isBottom = true
addFriendMenu.render = () => {
return this.$createElement('i', { class: 'lemon-icon-attah' })
}
addFriendMenu.renderContainer = () => {
return this.$createElement(AddFriend)
}
this.chatMenus.push(lastMsgMenu, contractsMenu, addFriendMenu)
}
private handleStartConnection() {
if (!this.connection) {
const builder = new HubConnectionBuilder()
const userToken = UserModule.token.replace('Bearer ', '')
this.connection = builder
.withUrl('/signalr-hubs/signalr-hubs/messages', { accessTokenFactory: () => userToken })
.withAutomaticReconnect({ nextRetryDelayInMilliseconds: () => 60000 })
.build()
this.connection.on('get-chat-message', this.handleReceiveMessage)
this.connection.onreconnected(() => {
this.handleInitIMUI()
})
this.connection.onclose(error => {
console.log('signalr connection has closed, error:')
console.log(error)
})
}
if (this.connection.state !== HubConnectionState.Connected) {
this.connection
.start()
.then(() => {
this.handleInitIMUI()
})
}
}
private onShowImDialog() {
this.showDialog = !this.showDialog
}
private onChangeMenu() {
console.log('Event:change-menu')
}
private onPullMessages(contact: any, next: any) {
if (this.getChatMessageFilter.receiveUserId !== contact.id) {
this.getChatMessageFilter.receiveUserId = contact.id
} else {
contact.readMsgPage += 1
}
this.getChatMessageFilter.skipCount = abpPagerFormat(contact.readMsgPage, this.getChatMessageFilter.maxResultCount)
ImApiService
.getMyChatMessages(this.getChatMessageFilter)
.then(res => {
const messages = res.items
.sort((last, next) => {
return next.sendTime < last.sendTime ? 1 : -1
})
.map(msg => {
const message = new Message()
message.fromChatMessage(msg)
return message
})
const isEnd = res.items.length === 0
setTimeout(() => {
next(messages, isEnd)
}, 1000)
})
.catch(() => {
setTimeout(() => {
next([], true)
}, 1000)
})
}
private handleMenuAvatarClick() {
console.log('Event:menu-avatar-click')
}
private handleMessageClick(e: any, key: any, message: Message) {
const imui = this.$refs.IMUI as any
if (key === 'status') {
imui.updateMessage(message.id, message.toContactId, {
status: 'going'
})
const chatMessage = Message.tryParseToChatMessage(message)
this.connection
.invoke('send', chatMessage)
.then(() => {
imui
.updateMessage(message.id, message.toContactId, {
status: 'succeed'
})
})
.catch(() => {
imui
.updateMessage(message.id, message.toContactId, {
status: 'failed'
})
})
}
}
private handleReceiveMessage(chatMessage: ChatMessage) {
this.trigger('onReceivedChatMessage', chatMessage)
}
private handleSendMessage(message: Message, next: any, file: any) {
const imui = this.$refs.IMUI as any
const chatMessage = Message.tryParseToChatMessage(message)
this.connection
.invoke('send', chatMessage)
.then(() => {
setTimeout(() => {
next()
}, 1000)
})
.catch(() => {
imui
.updateMessage(message.id, message.toContactId, {
status: 'failed'
})
})
}
private onReceivedChatMessage(chatMessage: ChatMessage) {
const message = new Message()
message.fromChatMessage(chatMessage)
const imui = this.$refs.IMUI as any
imui.appendMessage(message, chatMessage.formUserId)
const currentContact = imui.currentContact
if (currentContact && currentContact.id === chatMessage.formUserId) {
currentContact.lastContent = imui.lastContentRender(message)
currentContact.lastSendTime = new Date(chatMessage.sendTime).getTime()
} else {
imui.updateContact(chatMessage.formUserId, {
unread: '+1',
lastSendTime: new Date(chatMessage.sendTime).getTime(),
lastContent: imui.lastContentRender(message)
})
}
}
private onNewFriendValidation(notify: any) {
this.$confirm(
notify.data.properties.message,
this.$t('AbpIdentity.AreYouSure').toString(),
{
callback: action => {
if (action === 'confirm') {
const addUserFriend = new AddUserFriend(notify.data.properties.userId)
ImApiService
.addFriend(addUserFriend)
.then(() => {
this.$message.success('已添加对方为好友!')
})
}
}
}
)
}
private onChangeContract(contract: any) {
const imui = this.$refs.IMUI as any
imui.updateContact(contract.id, {
unread: 0
})
imui.closeDrawer()
imui.forceUpdateMessage()
}
}
</script>
<style lang="scss" scoped>
.body {
background: #3d495c !important
}
.link {
padding: 15px 0
}
.link >>> .a {
display: inline-block;
font-size: 16px;
color: #ccd3dc;
text-decoration: none;
border-radius: 5px;
margin-right: 15px;
&:hover {
color: #85acda;
}
}
.action {
margin-top: 30px;
.button {
margin-right: 10px
}
}
.imui-center {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
}
.drawer-content {
padding: 15px
}
.more {
font-size: 32px;
line-height: 18px;
height: 32px;
position: absolute;
top: 6px;
right: 14px;
cursor: pointer;
user-select: none;
color: #999;
&:active {
color: #000;
}
}
.bar {
text-align: center;
line-height: 30px;
background: #fff;
margin: 15px;
color: #666;
user-select: none;
font-size: 12px;
}
.cover {
text-align: center;
user-select: none;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
i {
font-size: 84px;
color: #e6e6e6;
}
p {
font-size: 18px;
color: #ddd;
line-height: 50px;
}
}
.article-item {
line-height: 34px;
cursor: pointer;
&:hover {
text-decoration: underline;
color: #318efd;
}
}
</style>