13 changed files with 177 additions and 543 deletions
@ -0,0 +1,19 @@ |
|||
package com.bx.implatform.config; |
|||
|
|||
|
|||
import lombok.Data; |
|||
|
|||
|
|||
@Data |
|||
public class ICEServer { |
|||
|
|||
|
|||
private String urls; |
|||
|
|||
|
|||
private String username; |
|||
|
|||
|
|||
private String credential; |
|||
|
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
package com.bx.implatform.config; |
|||
|
|||
import lombok.Data; |
|||
import org.springframework.boot.context.properties.ConfigurationProperties; |
|||
import org.springframework.stereotype.Component; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
|
|||
@Data |
|||
@Component |
|||
@ConfigurationProperties(prefix="webrtc") |
|||
public class ICEServerConfig { |
|||
|
|||
private List<ICEServer> iceServers = new ArrayList<>(); |
|||
|
|||
} |
|||
Binary file not shown.
Binary file not shown.
@ -1,369 +0,0 @@ |
|||
<template> |
|||
<div class="im-msg-item"> |
|||
<div class="im-msg-tip" v-show="msgInfo.type==10">{{msgInfo.content}}</div> |
|||
<div class="im-msg-normal" v-show="msgInfo.type!=10" :class="{'im-chat-mine':mine}"> |
|||
<div class="head-image"> |
|||
<head-image :url="headImage" :id="msgInfo.sendId"></head-image> |
|||
</div> |
|||
<div class="im-msg-content"> |
|||
<div class="im-msg-top"> |
|||
<span>{{showName}}</span> |
|||
<chat-time :time="msgInfo.sendTime"></chat-time> |
|||
</div> |
|||
<div class="im-msg-bottom" @contextmenu.prevent="showRightMenu($event)"> |
|||
<span class="im-msg-text" v-if="msgInfo.type==0" v-html="$emo.transform(msgInfo.content)"></span> |
|||
<div class="im-msg-image" v-if="msgInfo.type==1"> |
|||
<div class="img-load-box" v-loading="loading" element-loading-text="上传中.." element-loading-background="rgba(0, 0, 0, 0.4)"> |
|||
<img class="send-image" :src="JSON.parse(msgInfo.content).thumbUrl" @click="showFullImageBox()" /> |
|||
</div> |
|||
<span title="发送失败" v-show="loadFail" @click="handleSendFail" class="send-fail el-icon-warning"></span> |
|||
</div> |
|||
<div class="im-msg-file" v-if="msgInfo.type==2"> |
|||
<div class="im-file-box" v-loading="loading"> |
|||
<div class="im-file-info"> |
|||
<el-link class="im-file-name" :underline="true" target="_blank" type="primary" :href="data.url">{{data.name}}</el-link> |
|||
<div class="im-file-size">{{fileSize}}</div> |
|||
</div> |
|||
<div class="im-file-icon"> |
|||
<span type="primary" class="el-icon-document"></span> |
|||
</div> |
|||
</div> |
|||
<span title="发送失败" v-show="loadFail" @click="handleSendFail" class="send-fail el-icon-warning"></span> |
|||
</div> |
|||
<div class="im-msg-voice" v-if="msgInfo.type==3" @click="handlePlayVoice()"> |
|||
<audio controls :src="JSON.parse(msgInfo.content).url"></audio> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
</div> |
|||
<right-menu v-show="menu && rightMenu.show" :pos="rightMenu.pos" :items="menuItems" @close="rightMenu.show=false" |
|||
@select="handleSelectMenu"></right-menu> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import ChatTime from "./ChatTime.vue"; |
|||
import HeadImage from "../common/HeadImage.vue"; |
|||
import RightMenu from '../common/RightMenu.vue'; |
|||
|
|||
export default { |
|||
name: "messageItem", |
|||
components: { |
|||
ChatTime, |
|||
HeadImage, |
|||
RightMenu |
|||
}, |
|||
props: { |
|||
mine: { |
|||
type: Boolean, |
|||
required: true |
|||
}, |
|||
headImage: { |
|||
type: String, |
|||
required: true |
|||
}, |
|||
showName: { |
|||
type: String, |
|||
required: true |
|||
}, |
|||
msgInfo: { |
|||
type: Object, |
|||
required: true |
|||
}, |
|||
menu:{ |
|||
type: Boolean, |
|||
default: true |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
audioPlayState: 'STOP', |
|||
rightMenu: { |
|||
show: false, |
|||
pos: { |
|||
x: 0, |
|||
y: 0 |
|||
} |
|||
} |
|||
} |
|||
|
|||
}, |
|||
methods: { |
|||
handleSendFail() { |
|||
this.$message.error("该文件已发送失败,目前不支持自动重新发送,建议手动重新发送") |
|||
}, |
|||
showFullImageBox() { |
|||
let imageUrl = JSON.parse(this.msgInfo.content).originUrl; |
|||
if (imageUrl) { |
|||
this.$store.commit('showFullImageBox', imageUrl); |
|||
} |
|||
}, |
|||
handlePlayVoice() { |
|||
if (!this.audio) { |
|||
this.audio = new Audio(); |
|||
} |
|||
this.audio.src = JSON.parse(this.msgInfo.content).url; |
|||
this.audio.play(); |
|||
this.handlePlayVoice = 'RUNNING'; |
|||
}, |
|||
showRightMenu(e) { |
|||
this.rightMenu.pos = { |
|||
x: e.x, |
|||
y: e.y |
|||
}; |
|||
this.rightMenu.show = "true"; |
|||
}, |
|||
handleSelectMenu(item) { |
|||
this.$emit(item.key.toLowerCase(), this.msgInfo); |
|||
} |
|||
}, |
|||
computed: { |
|||
loading() { |
|||
return this.msgInfo.loadStatus && this.msgInfo.loadStatus === "loading"; |
|||
}, |
|||
loadFail() { |
|||
return this.msgInfo.loadStatus && this.msgInfo.loadStatus === "fail"; |
|||
}, |
|||
data() { |
|||
return JSON.parse(this.msgInfo.content) |
|||
}, |
|||
fileSize() { |
|||
let size = this.data.size; |
|||
if (size > 1024 * 1024) { |
|||
return Math.round(size / 1024 / 1024) + "M"; |
|||
} |
|||
if (size > 1024) { |
|||
return Math.round(size / 1024) + "KB"; |
|||
} |
|||
return size + "B"; |
|||
}, |
|||
menuItems() { |
|||
let items = []; |
|||
items.push({ |
|||
key: 'DELETE', |
|||
name: '删除', |
|||
icon: 'el-icon-delete' |
|||
}); |
|||
if (this.msgInfo.selfSend && this.msgInfo.id > 0) { |
|||
items.push({ |
|||
key: 'RECALL', |
|||
name: '撤回', |
|||
icon: 'el-icon-refresh-left' |
|||
}); |
|||
} |
|||
return items; |
|||
} |
|||
}, |
|||
mounted() { |
|||
//console.log(this.msgInfo); |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
.im-msg-item { |
|||
|
|||
.im-msg-tip { |
|||
line-height: 50px; |
|||
} |
|||
|
|||
.im-msg-normal { |
|||
position: relative; |
|||
font-size: 0; |
|||
margin-bottom: 10px; |
|||
padding-left: 60px; |
|||
min-height: 68px; |
|||
|
|||
.head-image { |
|||
position: absolute; |
|||
width: 40px; |
|||
height: 40px; |
|||
top: 0; |
|||
left: 0; |
|||
} |
|||
|
|||
.im-msg-content { |
|||
display: flex; |
|||
flex-direction: column; |
|||
|
|||
.im-msg-top { |
|||
display: flex; |
|||
flex-wrap: nowrap; |
|||
color: #333; |
|||
font-size: 14px; |
|||
line-height: 20px; |
|||
|
|||
span { |
|||
margin-right: 12px; |
|||
} |
|||
} |
|||
|
|||
.im-msg-bottom { |
|||
text-align: left; |
|||
|
|||
.im-msg-text { |
|||
position: relative; |
|||
line-height: 22px; |
|||
margin-top: 10px; |
|||
padding: 10px; |
|||
background-color: #eeeeee; |
|||
border-radius: 3px; |
|||
color: #333; |
|||
display: inline-block; |
|||
font-size: 14px; |
|||
|
|||
&:after { |
|||
content: ""; |
|||
position: absolute; |
|||
left: -10px; |
|||
top: 13px; |
|||
width: 0; |
|||
height: 0; |
|||
border-style: solid dashed dashed; |
|||
border-color: #eeeeee transparent transparent; |
|||
overflow: hidden; |
|||
border-width: 10px; |
|||
} |
|||
} |
|||
|
|||
.im-msg-image { |
|||
display: flex; |
|||
flex-wrap: nowrap; |
|||
flex-direction: row; |
|||
align-items: center; |
|||
|
|||
.send-image { |
|||
min-width: 300px; |
|||
min-height: 200px; |
|||
max-width: 600px; |
|||
max-height: 400px; |
|||
border: #dddddd solid 1px; |
|||
cursor: pointer; |
|||
} |
|||
|
|||
.send-fail { |
|||
color: #e60c0c; |
|||
font-size: 30px; |
|||
cursor: pointer; |
|||
margin: 0 20px; |
|||
} |
|||
} |
|||
|
|||
.im-msg-file { |
|||
display: flex; |
|||
flex-wrap: nowrap; |
|||
flex-direction: row; |
|||
align-items: center; |
|||
cursor: pointer; |
|||
|
|||
.im-file-box { |
|||
display: flex; |
|||
flex-wrap: nowrap; |
|||
align-items: center; |
|||
width: 20%; |
|||
min-height: 80px; |
|||
border: #dddddd solid 1px; |
|||
border-radius: 3px; |
|||
background-color: #eeeeee; |
|||
padding: 10px 15px; |
|||
|
|||
.im-file-info { |
|||
flex: 1; |
|||
height: 100%; |
|||
text-align: left; |
|||
font-size: 14px; |
|||
|
|||
.im-file-name { |
|||
font-size: 16px; |
|||
font-weight: 600; |
|||
margin-bottom: 15px; |
|||
} |
|||
} |
|||
|
|||
.im-file-icon { |
|||
font-size: 50px; |
|||
color: #d42e07; |
|||
} |
|||
} |
|||
|
|||
.send-fail { |
|||
color: #e60c0c; |
|||
font-size: 30px; |
|||
cursor: pointer; |
|||
margin: 0 20px; |
|||
} |
|||
|
|||
} |
|||
|
|||
.im-msg-voice { |
|||
font-size: 14px; |
|||
cursor: pointer; |
|||
|
|||
audio { |
|||
height: 45px; |
|||
padding: 5px 0; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
&.im-chat-mine { |
|||
text-align: right; |
|||
padding-left: 0; |
|||
padding-right: 60px; |
|||
|
|||
.head-image { |
|||
left: auto; |
|||
right: 0; |
|||
} |
|||
|
|||
.im-msg-content { |
|||
|
|||
.im-msg-top { |
|||
flex-direction: row-reverse; |
|||
|
|||
span { |
|||
margin-left: 12px; |
|||
margin-right: 0; |
|||
} |
|||
} |
|||
|
|||
.im-msg-bottom { |
|||
text-align: right; |
|||
|
|||
.im-msg-text { |
|||
margin-left: 10px; |
|||
background-color: #5fb878; |
|||
color: #fff; |
|||
display: inline-block; |
|||
vertical-align: top; |
|||
font-size: 14px; |
|||
|
|||
&:after { |
|||
left: auto; |
|||
right: -10px; |
|||
border-top-color: #5fb878; |
|||
} |
|||
} |
|||
|
|||
.im-msg-image { |
|||
flex-direction: row-reverse; |
|||
} |
|||
|
|||
.im-msg-file { |
|||
flex-direction: row-reverse; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.message-info { |
|||
right: 60px !important; |
|||
display: inline-block; |
|||
} |
|||
} |
|||
|
|||
} |
|||
} |
|||
</style> |
|||
@ -1,124 +0,0 @@ |
|||
<template> |
|||
<div class="video-acceptor"> |
|||
<div> |
|||
<head-image :size="120" :url="this.friend.headImage" :id="this.friend.id"></head-image> |
|||
</div> |
|||
<div> |
|||
{{friend.nickName}} 请求和您进行视频通话... |
|||
</div> |
|||
<div class="video-acceptor-btn-group"> |
|||
<div class="icon iconfont icon-phone-accept accept" @click="accpet()"></div> |
|||
<div class="icon iconfont icon-phone-reject reject" @click="reject()"></div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import HeadImage from '../common/HeadImage.vue'; |
|||
|
|||
export default { |
|||
name: "videoAcceptor", |
|||
components:{HeadImage}, |
|||
props: { |
|||
friend:{ |
|||
type: Object |
|||
} |
|||
}, |
|||
data(){ |
|||
return { |
|||
offer:{} |
|||
} |
|||
}, |
|||
methods:{ |
|||
accpet(){ |
|||
let info ={ |
|||
friend: this.friend, |
|||
master: false, |
|||
offer: this.offer |
|||
} |
|||
this.$store.commit("showChatPrivateVideoBox",info); |
|||
this.close(); |
|||
}, |
|||
reject(){ |
|||
this.$http({ |
|||
url: `/webrtc/private/reject?uid=${this.friend.id}`, |
|||
method: 'post' |
|||
}) |
|||
this.close(); |
|||
}, |
|||
failed(reason){ |
|||
this.$http({ |
|||
url: `/webrtc/private/failed?uid=${this.friend.id}&reason=${reason}`, |
|||
method: 'post' |
|||
}) |
|||
this.close(); |
|||
}, |
|||
onCall(msgInfo){ |
|||
console.log("onCall") |
|||
this.offer = JSON.parse(msgInfo.content); |
|||
if(this.$store.state.userStore.state == this.$enums.USER_STATE.BUSY){ |
|||
this.failed("对方正忙,暂时无法接听"); |
|||
return; |
|||
} |
|||
// 超时未接听 |
|||
this.timer && clearTimeout(this.timer); |
|||
this.timer = setTimeout(()=>{ |
|||
this.failed("对方未接听"); |
|||
},30000) |
|||
}, |
|||
onCancel(){ |
|||
this.$message.success("对方取消了呼叫"); |
|||
this.close(); |
|||
}, |
|||
handleMessage(msgInfo){ |
|||
if(msgInfo.type == this.$enums.MESSAGE_TYPE.RTC_CALL){ |
|||
this.onCall(msgInfo); |
|||
}else if(msgInfo.type == this.$enums.MESSAGE_TYPE.RTC_CANCEL){ |
|||
this.onCancel(); |
|||
} |
|||
}, |
|||
close(){ |
|||
this.timer && clearTimeout(this.timer); |
|||
this.$emit("close"); |
|||
} |
|||
} |
|||
} |
|||
|
|||
</script> |
|||
|
|||
<style scoped lang="scss"> |
|||
.video-acceptor { |
|||
position: absolute; |
|||
right: 1px; |
|||
bottom: 1px; |
|||
width: 250px; |
|||
height: 250px; |
|||
padding: 20px; |
|||
text-align: center; |
|||
background-color: #eeeeee; |
|||
border: #dddddd solid 1px; |
|||
|
|||
.video-acceptor-btn-group { |
|||
display: flex; |
|||
justify-content: space-around; |
|||
margin-top: 20px; |
|||
|
|||
.icon { |
|||
font-size: 50px; |
|||
cursor: pointer; |
|||
&.accept { |
|||
color: green; |
|||
} |
|||
&.reject { |
|||
color: red; |
|||
} |
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,72 @@ |
|||
import Vue from 'vue' |
|||
|
|||
// v-dialogDrag: 弹窗拖拽
|
|||
Vue.directive('dialogDrag', { |
|||
bind (el, binding, vnode, oldVnode) { |
|||
const dialogHeaderEl = el.querySelector('.el-dialog__header') |
|||
const dragDom = el.querySelector('.el-dialog') |
|||
dialogHeaderEl.style.cursor = 'move' |
|||
|
|||
// 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null);
|
|||
const sty = dragDom.currentStyle || window.getComputedStyle(dragDom, null) |
|||
|
|||
dialogHeaderEl.onmousedown = (e) => { |
|||
// 鼠标按下,计算当前元素距离可视区的距离
|
|||
const disX = e.clientX - dialogHeaderEl.offsetLeft |
|||
const disY = e.clientY - dialogHeaderEl.offsetTop |
|||
const screenWidth = document.body.clientWidth; // body当前宽度
|
|||
const screenHeight = document.documentElement.clientHeight; // 可见区域高度(应为body高度,可某些环境下无法获取)
|
|||
const dragDomWidth = dragDom.offsetWidth; // 对话框宽度
|
|||
const dragDomheight = dragDom.offsetHeight; // 对话框高度
|
|||
const minDragDomLeft = dragDom.offsetLeft; |
|||
const maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDomWidth; |
|||
const minDragDomTop = dragDom.offsetTop; |
|||
const maxDragDomTop = screenHeight - dragDom.offsetTop - dragDomheight; |
|||
|
|||
// 获取到的值带px 正则匹配替换
|
|||
let styL, styT |
|||
|
|||
// 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px
|
|||
if (sty.left.includes('%')) { |
|||
styL = +document.body.clientWidth * (+sty.left.replace(/\%/g, '') / 100) |
|||
styT = +document.body.clientHeight * (+sty.top.replace(/\%/g, '') / 100) |
|||
} else { |
|||
styL = +sty.left.replace(/\px/g, '') |
|||
styT = +sty.top.replace(/\px/g, '') |
|||
} |
|||
|
|||
document.onmousemove = function (e) { |
|||
// 获取body的页面可视宽高
|
|||
// var clientHeight = document.documentElement.clientHeight || document.body.clientHeight
|
|||
// var clientWidth = document.documentElement.clientWidth || document.body.clientWidth
|
|||
|
|||
// 通过事件委托,计算移动的距离
|
|||
var l = e.clientX - disX |
|||
var t = e.clientY - disY |
|||
|
|||
// 边界处理
|
|||
if (-l > minDragDomLeft) { |
|||
l = -minDragDomLeft; |
|||
} else if (l > maxDragDomLeft) { |
|||
l = maxDragDomLeft; |
|||
} |
|||
if (-t > minDragDomTop) { |
|||
t = -minDragDomTop; |
|||
} else if (t > maxDragDomTop) { |
|||
t = maxDragDomTop; |
|||
} |
|||
// 移动当前元素
|
|||
dragDom.style.left = `${l + styL}px` |
|||
dragDom.style.top = `${t + styT}px` |
|||
|
|||
// 将此时的位置传出去
|
|||
// binding.value({x:e.pageX,y:e.pageY})
|
|||
} |
|||
|
|||
document.onmouseup = function (e) { |
|||
document.onmousemove = null |
|||
document.onmouseup = null |
|||
} |
|||
} |
|||
} |
|||
}) |
|||
Loading…
Reference in new issue