58 changed files with 3866 additions and 3897 deletions
@ -1,131 +1,131 @@ |
|||
<template> |
|||
<el-scrollbar v-show="show&&showMembers.length" ref="scrollBox" class="group-member-choose" |
|||
:style="{'left':pos.x+'px','top':pos.y-300+'px'}"> |
|||
<div v-for="(member,idx) in showMembers" :key="member.id"> |
|||
<chat-group-member :member="member" :height="40" :active='activeIdx==idx' |
|||
@click.native="onSelectMember(member)"></chat-group-member> |
|||
<el-scrollbar v-show="show && showMembers.length" ref="scrollBox" class="group-member-choose" |
|||
:style="{ 'left': pos.x + 'px', 'top': pos.y - 300 + 'px' }"> |
|||
<div v-for="(member, idx) in showMembers" :key="member.id"> |
|||
<chat-group-member :member="member" :height="40" :active='activeIdx == idx' |
|||
@click.native="onSelectMember(member)"></chat-group-member> |
|||
</div> |
|||
</el-scrollbar> |
|||
</template> |
|||
|
|||
<script> |
|||
import ChatGroupMember from "./ChatGroupMember.vue"; |
|||
export default { |
|||
name: "chatAtBox", |
|||
components: { |
|||
ChatGroupMember |
|||
import ChatGroupMember from "./ChatGroupMember.vue"; |
|||
export default { |
|||
name: "chatAtBox", |
|||
components: { |
|||
ChatGroupMember |
|||
}, |
|||
props: { |
|||
searchText: { |
|||
type: String, |
|||
default: "" |
|||
}, |
|||
props: { |
|||
searchText: { |
|||
type: String, |
|||
default: "" |
|||
}, |
|||
ownerId: { |
|||
type: Number, |
|||
ownerId: { |
|||
type: Number, |
|||
}, |
|||
members: { |
|||
type: Array |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
show: false, |
|||
pos: { |
|||
x: 0, |
|||
y: 0 |
|||
}, |
|||
members: { |
|||
type: Array |
|||
activeIdx: 0, |
|||
showMembers: [] |
|||
}; |
|||
}, |
|||
methods: { |
|||
init() { |
|||
this.$refs.scrollBox.wrap.scrollTop = 0; |
|||
this.showMembers = []; |
|||
let userId = this.$store.state.userStore.userInfo.id; |
|||
let name = "全体成员"; |
|||
if (this.ownerId == userId && name.startsWith(this.searchText)) { |
|||
this.showMembers.push({ |
|||
userId: -1, |
|||
showNickName: name |
|||
}) |
|||
} |
|||
this.members.forEach((m) => { |
|||
if (m.userId != userId && !m.quit && m.showNickName.startsWith(this.searchText)) { |
|||
this.showMembers.push(m); |
|||
} |
|||
}) |
|||
this.activeIdx = this.showMembers.length > 0 ? 0 : -1; |
|||
}, |
|||
data() { |
|||
return { |
|||
show: false, |
|||
pos: { |
|||
x: 0, |
|||
y: 0 |
|||
}, |
|||
activeIdx: 0, |
|||
showMembers: [] |
|||
}; |
|||
open(pos) { |
|||
this.show = true; |
|||
this.pos = pos; |
|||
this.init(); |
|||
}, |
|||
methods: { |
|||
init() { |
|||
this.$refs.scrollBox.wrap.scrollTop = 0; |
|||
this.showMembers = []; |
|||
let userId = this.$store.state.userStore.userInfo.id; |
|||
let name = "全体成员"; |
|||
if (this.ownerId == userId && name.startsWith(this.searchText)) { |
|||
this.showMembers.push({ |
|||
userId: -1, |
|||
showNickName: name |
|||
}) |
|||
} |
|||
this.members.forEach((m) => { |
|||
if (m.userId != userId && !m.quit && m.showNickName.startsWith(this.searchText)) { |
|||
this.showMembers.push(m); |
|||
} |
|||
}) |
|||
this.activeIdx = this.showMembers.length > 0 ? 0: -1; |
|||
}, |
|||
open(pos) { |
|||
this.show = true; |
|||
this.pos = pos; |
|||
this.init(); |
|||
}, |
|||
close() { |
|||
this.show = false; |
|||
}, |
|||
moveUp() { |
|||
if (this.activeIdx > 0) { |
|||
this.activeIdx--; |
|||
this.scrollToActive() |
|||
} |
|||
}, |
|||
moveDown() { |
|||
if (this.activeIdx < this.showMembers.length - 1) { |
|||
this.activeIdx++; |
|||
this.scrollToActive() |
|||
} |
|||
}, |
|||
select() { |
|||
if (this.activeIdx >= 0) { |
|||
this.onSelectMember(this.showMembers[this.activeIdx]) |
|||
} |
|||
this.close(); |
|||
}, |
|||
scrollToActive() { |
|||
if (this.activeIdx * 35 - this.$refs.scrollBox.wrap.clientHeight > this.$refs.scrollBox.wrap.scrollTop) { |
|||
this.$refs.scrollBox.wrap.scrollTop += 140; |
|||
if (this.$refs.scrollBox.wrap.scrollTop > this.$refs.scrollBox.wrap.scrollHeight) { |
|||
this.$refs.scrollBox.wrap.scrollTop = this.$refs.scrollBox.wrap.scrollHeight |
|||
} |
|||
} |
|||
if (this.activeIdx * 35 < this.$refs.scrollBox.wrap.scrollTop) { |
|||
this.$refs.scrollBox.wrap.scrollTop -= 140; |
|||
if (this.$refs.scrollBox.wrap.scrollTop < 0) { |
|||
this.$refs.scrollBox.wrap.scrollTop = 0; |
|||
} |
|||
} |
|||
}, |
|||
onSelectMember(member) { |
|||
this.$emit("select", member); |
|||
this.show = false; |
|||
close() { |
|||
this.show = false; |
|||
}, |
|||
moveUp() { |
|||
if (this.activeIdx > 0) { |
|||
this.activeIdx--; |
|||
this.scrollToActive() |
|||
} |
|||
}, |
|||
computed: { |
|||
isOwner() { |
|||
return this.$store.state.userStore.userInfo.id == this.ownerId; |
|||
moveDown() { |
|||
if (this.activeIdx < this.showMembers.length - 1) { |
|||
this.activeIdx++; |
|||
this.scrollToActive() |
|||
} |
|||
}, |
|||
watch: { |
|||
searchText: { |
|||
handler(newText, oldText) { |
|||
this.init(); |
|||
select() { |
|||
if (this.activeIdx >= 0) { |
|||
this.onSelectMember(this.showMembers[this.activeIdx]) |
|||
} |
|||
this.close(); |
|||
}, |
|||
scrollToActive() { |
|||
if (this.activeIdx * 35 - this.$refs.scrollBox.wrap.clientHeight > this.$refs.scrollBox.wrap.scrollTop) { |
|||
this.$refs.scrollBox.wrap.scrollTop += 140; |
|||
if (this.$refs.scrollBox.wrap.scrollTop > this.$refs.scrollBox.wrap.scrollHeight) { |
|||
this.$refs.scrollBox.wrap.scrollTop = this.$refs.scrollBox.wrap.scrollHeight |
|||
} |
|||
} |
|||
if (this.activeIdx * 35 < this.$refs.scrollBox.wrap.scrollTop) { |
|||
this.$refs.scrollBox.wrap.scrollTop -= 140; |
|||
if (this.$refs.scrollBox.wrap.scrollTop < 0) { |
|||
this.$refs.scrollBox.wrap.scrollTop = 0; |
|||
} |
|||
} |
|||
}, |
|||
onSelectMember(member) { |
|||
this.$emit("select", member); |
|||
this.show = false; |
|||
} |
|||
}, |
|||
computed: { |
|||
isOwner() { |
|||
return this.$store.state.userStore.userInfo.id == this.ownerId; |
|||
} |
|||
}, |
|||
watch: { |
|||
searchText: { |
|||
handler(newText, oldText) { |
|||
this.init(); |
|||
} |
|||
} |
|||
|
|||
} |
|||
|
|||
} |
|||
</script> |
|||
|
|||
<style scoped lang="scss"> |
|||
.group-member-choose { |
|||
position: fixed; |
|||
width: 200px; |
|||
height: 300px; |
|||
//border: 1px solid #53a0e79c; |
|||
//border-radius: 5px; |
|||
background-color: #fff; |
|||
box-shadow: var(--im-box-shadow); |
|||
} |
|||
.group-member-choose { |
|||
position: fixed; |
|||
width: 200px; |
|||
height: 300px; |
|||
//border: 1px solid #53a0e79c; |
|||
//border-radius: 5px; |
|||
background-color: #fff; |
|||
box-shadow: var(--im-box-shadow); |
|||
} |
|||
</style> |
|||
|
|||
File diff suppressed because it is too large
File diff suppressed because it is too large
@ -1,139 +1,138 @@ |
|||
<template> |
|||
<el-dialog class="chat-record" title="语音录制" :visible.sync="visible" width="600px" :before-close="onClose"> |
|||
<div v-show="mode=='RECORD'"> |
|||
<div class="tip">{{stateTip}}</div> |
|||
<div>时长: {{state=='STOP'?0:parseInt(rc.duration)}}s</div> |
|||
<div v-show="mode == 'RECORD'"> |
|||
<div class="tip">{{ stateTip }}</div> |
|||
<div>时长: {{ state == 'STOP' ? 0 : parseInt(rc.duration) }}s</div> |
|||
</div> |
|||
<audio v-show="mode=='PLAY'" :src="url" controls ref="audio" @ended="onStopAudio()"></audio> |
|||
<audio v-show="mode == 'PLAY'" :src="url" controls ref="audio" @ended="onStopAudio()"></audio> |
|||
<el-divider content-position="center"></el-divider> |
|||
<el-row class="btn-group"> |
|||
<el-button round type="primary" v-show="state=='STOP'" @click="onStartRecord()">开始录音</el-button> |
|||
<el-button round type="warning" v-show="state=='RUNNING'" @click="onPauseRecord()">暂停录音</el-button> |
|||
<el-button round type="primary" v-show="state=='PAUSE'" @click="onResumeRecord()">继续录音</el-button> |
|||
<el-button round type="danger" v-show="state=='RUNNING'||state=='PAUSE'" @click="onCompleteRecord()"> |
|||
<el-button round type="primary" v-show="state == 'STOP'" @click="onStartRecord()">开始录音</el-button> |
|||
<el-button round type="warning" v-show="state == 'RUNNING'" @click="onPauseRecord()">暂停录音</el-button> |
|||
<el-button round type="primary" v-show="state == 'PAUSE'" @click="onResumeRecord()">继续录音</el-button> |
|||
<el-button round type="danger" v-show="state == 'RUNNING' || state == 'PAUSE'" @click="onCompleteRecord()"> |
|||
结束录音</el-button> |
|||
<el-button round type="success" v-show="state=='COMPLETE' && mode!='PLAY'" @click="onPlayAudio()">播放录音 |
|||
<el-button round type="success" v-show="state == 'COMPLETE' && mode != 'PLAY'" @click="onPlayAudio()">播放录音 |
|||
</el-button> |
|||
<el-button round type="warning" v-show="state=='COMPLETE' && mode=='PLAY'" @click="onStopAudio()">停止播放 |
|||
<el-button round type="warning" v-show="state == 'COMPLETE' && mode == 'PLAY'" @click="onStopAudio()">停止播放 |
|||
</el-button> |
|||
<el-button round type="primary" v-show="state=='COMPLETE'" @click="onRestartRecord()">重新录音</el-button> |
|||
<el-button round type="primary" v-show="state=='COMPLETE'" @click="onSendRecord()">立即发送</el-button> |
|||
<el-button round type="primary" v-show="state == 'COMPLETE'" @click="onRestartRecord()">重新录音</el-button> |
|||
<el-button round type="primary" v-show="state == 'COMPLETE'" @click="onSendRecord()">立即发送</el-button> |
|||
</el-row> |
|||
</el-dialog> |
|||
|
|||
</template> |
|||
|
|||
<script> |
|||
import Recorder from 'js-audio-recorder'; |
|||
import Recorder from 'js-audio-recorder'; |
|||
|
|||
export default { |
|||
name: 'chatRecord', |
|||
props: { |
|||
visible: { |
|||
type: Boolean |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
rc: new Recorder(), |
|||
audio: new Audio(), |
|||
state: 'STOP', // STOP、RUNNING、PAUSE、COMPLETE |
|||
stateTip: "未开始", |
|||
mode: 'RECORD', // RECORD 、PLAY |
|||
duration: 0, |
|||
url: "" |
|||
} |
|||
export default { |
|||
name: 'chatRecord', |
|||
props: { |
|||
visible: { |
|||
type: Boolean |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
rc: new Recorder(), |
|||
audio: new Audio(), |
|||
state: 'STOP', // STOP、RUNNING、PAUSE、COMPLETE |
|||
stateTip: "未开始", |
|||
mode: 'RECORD', // RECORD 、PLAY |
|||
duration: 0, |
|||
url: "" |
|||
} |
|||
}, |
|||
methods: { |
|||
onClose() { |
|||
// 关闭前清除数据 |
|||
this.rc.destroy(); |
|||
this.rc = new Recorder(); |
|||
this.audio.pause(); |
|||
this.mode = 'RECORD'; |
|||
this.state = 'STOP'; |
|||
this.stateTip = '未开始'; |
|||
this.$emit("close"); |
|||
}, |
|||
methods: { |
|||
onClose() { |
|||
// 关闭前清除数据 |
|||
this.rc.destroy(); |
|||
this.rc = new Recorder(); |
|||
this.audio.pause(); |
|||
this.mode = 'RECORD'; |
|||
this.state = 'STOP'; |
|||
this.stateTip = '未开始'; |
|||
this.$emit("close"); |
|||
}, |
|||
onStartRecord() { |
|||
this.rc.start().then((stream) => { |
|||
this.state = 'RUNNING'; |
|||
this.stateTip = "正在录音..."; |
|||
}).catch(error => { |
|||
this.$message.error(error); |
|||
}); |
|||
|
|||
|
|||
}, |
|||
onPauseRecord() { |
|||
this.rc.pause(); |
|||
this.state = 'PAUSE'; |
|||
this.stateTip = "已暂停录音"; |
|||
}, |
|||
onResumeRecord() { |
|||
this.rc.resume(); |
|||
onStartRecord() { |
|||
this.rc.start().then((stream) => { |
|||
this.state = 'RUNNING'; |
|||
this.stateTip = "正在录音..."; |
|||
}, |
|||
onCompleteRecord() { |
|||
this.rc.pause(); |
|||
this.state = 'COMPLETE'; |
|||
this.stateTip = "已结束录音"; |
|||
}, |
|||
onPlayAudio() { |
|||
let wav = this.rc.getWAVBlob(); |
|||
let url = URL.createObjectURL(wav); |
|||
this.$refs.audio.src = url; |
|||
this.$refs.audio.play(); |
|||
this.mode = 'PLAY'; |
|||
}, |
|||
onStopAudio() { |
|||
this.$refs.audio.pause(); |
|||
this.mode = 'RECORD'; |
|||
}, |
|||
onRestartRecord() { |
|||
this.rc.destroy(); |
|||
this.rc = new Recorder() |
|||
this.rc.start(); |
|||
this.state = 'RUNNING'; |
|||
this.mode = 'RECORD'; |
|||
this.stateTip = "正在录音..."; |
|||
}, |
|||
onSendRecord() { |
|||
let wav = this.rc.getWAVBlob(); |
|||
let name = new Date().getDate() + '.wav'; |
|||
var formData = new window.FormData() |
|||
formData.append('file', wav, name); |
|||
this.$http({ |
|||
url: '/file/upload', |
|||
data: formData, |
|||
method: 'post', |
|||
headers: { |
|||
'Content-Type': 'multipart/form-data' |
|||
} |
|||
}).then((url) => { |
|||
let data = { |
|||
duration: parseInt(this.rc.duration), |
|||
url: url |
|||
} |
|||
this.$emit("send", data); |
|||
this.onClose(); |
|||
}) |
|||
} |
|||
} |
|||
}).catch(error => { |
|||
this.$message.error(error); |
|||
}); |
|||
|
|||
|
|||
}, |
|||
onPauseRecord() { |
|||
this.rc.pause(); |
|||
this.state = 'PAUSE'; |
|||
this.stateTip = "已暂停录音"; |
|||
}, |
|||
onResumeRecord() { |
|||
this.rc.resume(); |
|||
this.state = 'RUNNING'; |
|||
this.stateTip = "正在录音..."; |
|||
}, |
|||
onCompleteRecord() { |
|||
this.rc.pause(); |
|||
this.state = 'COMPLETE'; |
|||
this.stateTip = "已结束录音"; |
|||
}, |
|||
onPlayAudio() { |
|||
let wav = this.rc.getWAVBlob(); |
|||
let url = URL.createObjectURL(wav); |
|||
this.$refs.audio.src = url; |
|||
this.$refs.audio.play(); |
|||
this.mode = 'PLAY'; |
|||
}, |
|||
onStopAudio() { |
|||
this.$refs.audio.pause(); |
|||
this.mode = 'RECORD'; |
|||
}, |
|||
onRestartRecord() { |
|||
this.rc.destroy(); |
|||
this.rc = new Recorder() |
|||
this.rc.start(); |
|||
this.state = 'RUNNING'; |
|||
this.mode = 'RECORD'; |
|||
this.stateTip = "正在录音..."; |
|||
}, |
|||
onSendRecord() { |
|||
let wav = this.rc.getWAVBlob(); |
|||
let name = new Date().getDate() + '.wav'; |
|||
var formData = new window.FormData() |
|||
formData.append('file', wav, name); |
|||
this.$http({ |
|||
url: '/file/upload', |
|||
data: formData, |
|||
method: 'post', |
|||
headers: { |
|||
'Content-Type': 'multipart/form-data' |
|||
} |
|||
}).then((url) => { |
|||
let data = { |
|||
duration: parseInt(this.rc.duration), |
|||
url: url |
|||
} |
|||
this.$emit("send", data); |
|||
this.onClose(); |
|||
}) |
|||
} |
|||
} |
|||
|
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
.chat-record { |
|||
.chat-record { |
|||
|
|||
.tip { |
|||
font-size: 18px; |
|||
} |
|||
.tip { |
|||
font-size: 18px; |
|||
} |
|||
|
|||
.btn-group { |
|||
margin-bottom: 20px; |
|||
} |
|||
.btn-group { |
|||
margin-bottom: 20px; |
|||
} |
|||
} |
|||
</style> |
|||
|
|||
@ -1,103 +1,102 @@ |
|||
<template> |
|||
<el-upload :action="'#'" :http-request="onFileUpload" :accept="fileTypes==null?'':fileTypes.join(',')" :show-file-list="false" |
|||
:disabled="disabled" :before-upload="beforeUpload" :multiple="true"> |
|||
<el-upload :action="'#'" :http-request="onFileUpload" :accept="fileTypes == null ? '' : fileTypes.join(',')" |
|||
:show-file-list="false" :disabled="disabled" :before-upload="beforeUpload" :multiple="true"> |
|||
<slot></slot> |
|||
</el-upload> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name: "fileUpload", |
|||
data() { |
|||
return { |
|||
loading: null, |
|||
uploadHeaders: { |
|||
"accessToken": sessionStorage.getItem('accessToken') |
|||
} |
|||
export default { |
|||
name: "fileUpload", |
|||
data() { |
|||
return { |
|||
loading: null, |
|||
uploadHeaders: { |
|||
"accessToken": sessionStorage.getItem('accessToken') |
|||
} |
|||
} |
|||
}, |
|||
props: { |
|||
action: { |
|||
type: String, |
|||
required: false |
|||
}, |
|||
props: { |
|||
action: { |
|||
type: String, |
|||
required: false |
|||
}, |
|||
fileTypes: { |
|||
type: Array, |
|||
default: null |
|||
}, |
|||
maxSize: { |
|||
type: Number, |
|||
default: null |
|||
}, |
|||
showLoading: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
disabled: { |
|||
type: Boolean, |
|||
default: false |
|||
} |
|||
fileTypes: { |
|||
type: Array, |
|||
default: null |
|||
}, |
|||
methods: { |
|||
onFileUpload(file) { |
|||
// 展示加载条 |
|||
if (this.showLoading) { |
|||
this.loading = this.$loading({ |
|||
lock: true, |
|||
text: '正在上传...', |
|||
spinner: 'el-icon-loading', |
|||
background: 'rgba(0, 0, 0, 0.7)' |
|||
}); |
|||
} |
|||
let formData = new FormData() |
|||
formData.append('file', file.file) |
|||
this.$http({ |
|||
url: this.action, |
|||
data: formData, |
|||
method: 'post', |
|||
headers: { |
|||
'Content-Type': 'multipart/form-data' |
|||
} |
|||
}).then((data) => { |
|||
this.$emit("success", data, file.file); |
|||
}).catch((e) => { |
|||
this.$emit("fail", e, file.file); |
|||
}).finally(() => { |
|||
this.loading && this.loading.close(); |
|||
}) |
|||
}, |
|||
beforeUpload(file) { |
|||
// 校验文件类型 |
|||
if (this.fileTypes && this.fileTypes.length > 0) { |
|||
let fileType = file.type; |
|||
let t = this.fileTypes.find((t) => t.toLowerCase() === fileType); |
|||
if (t === undefined) { |
|||
this.$message.error(`文件格式错误,请上传以下格式的文件:${this.fileTypes.join("、")}`); |
|||
return false; |
|||
} |
|||
maxSize: { |
|||
type: Number, |
|||
default: null |
|||
}, |
|||
showLoading: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
disabled: { |
|||
type: Boolean, |
|||
default: false |
|||
} |
|||
}, |
|||
methods: { |
|||
onFileUpload(file) { |
|||
// 展示加载条 |
|||
if (this.showLoading) { |
|||
this.loading = this.$loading({ |
|||
lock: true, |
|||
text: '正在上传...', |
|||
spinner: 'el-icon-loading', |
|||
background: 'rgba(0, 0, 0, 0.7)' |
|||
}); |
|||
} |
|||
let formData = new FormData() |
|||
formData.append('file', file.file) |
|||
this.$http({ |
|||
url: this.action, |
|||
data: formData, |
|||
method: 'post', |
|||
headers: { |
|||
'Content-Type': 'multipart/form-data' |
|||
} |
|||
// 校验大小 |
|||
if (this.maxSize && file.size > this.maxSize) { |
|||
this.$message.error(`文件大小不能超过 ${this.fileSizeStr}!`); |
|||
}).then((data) => { |
|||
this.$emit("success", data, file.file); |
|||
}).catch((e) => { |
|||
this.$emit("fail", e, file.file); |
|||
}).finally(() => { |
|||
this.loading && this.loading.close(); |
|||
}) |
|||
}, |
|||
beforeUpload(file) { |
|||
// 校验文件类型 |
|||
if (this.fileTypes && this.fileTypes.length > 0) { |
|||
let fileType = file.type; |
|||
let t = this.fileTypes.find((t) => t.toLowerCase() === fileType); |
|||
if (t === undefined) { |
|||
this.$message.error(`文件格式错误,请上传以下格式的文件:${this.fileTypes.join("、")}`); |
|||
return false; |
|||
} |
|||
this.$emit("before", file); |
|||
return true; |
|||
} |
|||
}, |
|||
computed: { |
|||
fileSizeStr() { |
|||
if (this.maxSize > 1024 * 1024) { |
|||
return Math.round(this.maxSize / 1024 / 1024) + "M"; |
|||
} |
|||
if (this.maxSize > 1024) { |
|||
return Math.round(this.maxSize / 1024) + "KB"; |
|||
} |
|||
return this.maxSize + "B"; |
|||
// 校验大小 |
|||
if (this.maxSize && file.size > this.maxSize) { |
|||
this.$message.error(`文件大小不能超过 ${this.fileSizeStr}!`); |
|||
return false; |
|||
} |
|||
this.$emit("before", file); |
|||
return true; |
|||
} |
|||
}, |
|||
computed: { |
|||
fileSizeStr() { |
|||
if (this.maxSize > 1024 * 1024) { |
|||
return Math.round(this.maxSize / 1024 / 1024) + "M"; |
|||
} |
|||
if (this.maxSize > 1024) { |
|||
return Math.round(this.maxSize / 1024) + "KB"; |
|||
} |
|||
return this.maxSize + "B"; |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style> |
|||
</style> |
|||
<style></style> |
|||
@ -1,70 +1,70 @@ |
|||
<template> |
|||
<div class="group-member"> |
|||
<head-image :id="member.userId" :name="member.showNickName" |
|||
:url="member.headImage" :size="38" |
|||
:online="member.online" > |
|||
<div v-if="showDel" @click.stop="onDelete()" class="btn-kick el-icon-error"></div> |
|||
<head-image :id="member.userId" :name="member.showNickName" :url="member.headImage" :size="38" |
|||
:online="member.online"> |
|||
<div v-if="showDel" @click.stop="onDelete()" class="btn-kick el-icon-error"></div> |
|||
</head-image> |
|||
<div class="member-name">{{member.showNickName}}</div> |
|||
<div class="member-name">{{ member.showNickName }}</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import HeadImage from "../common/HeadImage.vue"; |
|||
export default{ |
|||
name: "groupMember", |
|||
components:{HeadImage}, |
|||
data(){ |
|||
return {}; |
|||
import HeadImage from "../common/HeadImage.vue"; |
|||
export default { |
|||
name: "groupMember", |
|||
components: { HeadImage }, |
|||
data() { |
|||
return {}; |
|||
}, |
|||
props: { |
|||
member: { |
|||
type: Object, |
|||
required: true |
|||
}, |
|||
props:{ |
|||
member:{ |
|||
type: Object, |
|||
required: true |
|||
}, |
|||
showDel:{ |
|||
type: Boolean, |
|||
default: false |
|||
} |
|||
}, |
|||
methods:{ |
|||
onDelete(){ |
|||
this.$emit("del",this.member); |
|||
} |
|||
showDel: { |
|||
type: Boolean, |
|||
default: false |
|||
} |
|||
}, |
|||
methods: { |
|||
onDelete() { |
|||
this.$emit("del", this.member); |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
.group-member{ |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
width: 50px; |
|||
.member-name { |
|||
font-size: 12px; |
|||
text-align: center; |
|||
width: 100%; |
|||
height: 30px; |
|||
line-height: 30px; |
|||
white-space: nowrap; |
|||
text-overflow:ellipsis; |
|||
overflow:hidden |
|||
} |
|||
.group-member { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
width: 50px; |
|||
|
|||
.btn-kick { |
|||
display: none; |
|||
position: absolute; |
|||
right: -8px; |
|||
top: -8px; |
|||
color: darkred; |
|||
font-size: 20px; |
|||
cursor: pointer; |
|||
} |
|||
.member-name { |
|||
font-size: 12px; |
|||
text-align: center; |
|||
width: 100%; |
|||
height: 30px; |
|||
line-height: 30px; |
|||
white-space: nowrap; |
|||
text-overflow: ellipsis; |
|||
overflow: hidden |
|||
} |
|||
|
|||
&:hover .btn-kick{ |
|||
display: block; |
|||
color: #ce1818; |
|||
} |
|||
.btn-kick { |
|||
display: none; |
|||
position: absolute; |
|||
right: -8px; |
|||
top: -8px; |
|||
color: darkred; |
|||
font-size: 20px; |
|||
cursor: pointer; |
|||
} |
|||
|
|||
&:hover .btn-kick { |
|||
display: block; |
|||
color: #ce1818; |
|||
} |
|||
} |
|||
</style> |
|||
|
|||
@ -1,116 +1,118 @@ |
|||
<template> |
|||
<el-dialog title="是否加入通话?" :visible.sync="isShow" width="400px"> |
|||
<div class="rtc-group-join"> |
|||
<div class="host-info"> |
|||
<head-image :name="rtcInfo.host.nickName" :url="rtcInfo.host.headImage" :size="80"></head-image> |
|||
<div class="host-text">{{'发起人:'+rtcInfo.host.nickName}}</div> |
|||
<el-dialog title="是否加入通话?" :visible.sync="isShow" width="400px"> |
|||
<div class="rtc-group-join"> |
|||
<div class="host-info"> |
|||
<head-image :name="rtcInfo.host.nickName" :url="rtcInfo.host.headImage" :size="80"></head-image> |
|||
<div class="host-text">{{ '发起人:' + rtcInfo.host.nickName }}</div> |
|||
</div> |
|||
<div class="users-info"> |
|||
<div>{{ rtcInfo.userInfos.length + '人正在通话中' }}</div> |
|||
<div class="user-list"> |
|||
<div class="user-item" v-for="user in rtcInfo.userInfos" :key="user.id"> |
|||
<head-image :url="user.headImage" :name="user.nickName" :size="40"> |
|||
</head-image> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="users-info"> |
|||
<div>{{rtcInfo.userInfos.length+'人正在通话中'}}</div> |
|||
<div class="user-list"> |
|||
<div class="user-item" v-for="user in rtcInfo.userInfos" :key="user.id"> |
|||
<head-image :url="user.headImage" :name="user.nickName" :size="40"> |
|||
</head-image> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<span slot="footer" class="dialog-footer"> |
|||
<el-button @click="onCancel()">取 消</el-button> |
|||
<el-button type="primary" @click="onOk()">确 定</el-button> |
|||
</span> |
|||
<span slot="footer" class="dialog-footer"> |
|||
<el-button @click="onCancel()">取 消</el-button> |
|||
<el-button type="primary" @click="onOk()">确 定</el-button> |
|||
</span> |
|||
</el-dialog> |
|||
</template> |
|||
|
|||
<script> |
|||
import HeadImage from '@/components/common/HeadImage' |
|||
import HeadImage from '@/components/common/HeadImage' |
|||
|
|||
export default{ |
|||
name: "rtcGroupJoin", |
|||
components:{ |
|||
HeadImage |
|||
}, |
|||
data() { |
|||
return { |
|||
isShow: false, |
|||
rtcInfo: { |
|||
host:{}, |
|||
userInfos:[] |
|||
} |
|||
export default { |
|||
name: "rtcGroupJoin", |
|||
components: { |
|||
HeadImage |
|||
}, |
|||
data() { |
|||
return { |
|||
isShow: false, |
|||
rtcInfo: { |
|||
host: {}, |
|||
userInfos: [] |
|||
} |
|||
} |
|||
}, |
|||
props: { |
|||
groupId: { |
|||
type: Number |
|||
} |
|||
}, |
|||
methods: { |
|||
open(rtcInfo) { |
|||
this.rtcInfo = rtcInfo; |
|||
this.isShow = true; |
|||
}, |
|||
props: { |
|||
groupId: { |
|||
type: Number |
|||
onOk() { |
|||
this.isShow = false; |
|||
let userInfos = this.rtcInfo.userInfos; |
|||
let mine = this.$store.state.userStore.userInfo; |
|||
if (!userInfos.find((user) => user.id == mine.id)) { |
|||
// 加入自己的信息 |
|||
userInfos.push({ |
|||
id: mine.id, |
|||
nickName: mine.nickName, |
|||
headImage: mine.headImageThumb, |
|||
isCamera: false, |
|||
isMicroPhone: true |
|||
}) |
|||
} |
|||
}, |
|||
methods: { |
|||
open(rtcInfo) { |
|||
this.rtcInfo = rtcInfo; |
|||
this.isShow = true; |
|||
}, |
|||
onOk() { |
|||
this.isShow = false; |
|||
let userInfos = this.rtcInfo.userInfos; |
|||
let mine = this.$store.state.userStore.userInfo; |
|||
if(!userInfos.find((user)=>user.id==mine.id)){ |
|||
// 加入自己的信息 |
|||
userInfos.push({ |
|||
id: mine.id, |
|||
nickName: mine.nickName, |
|||
headImage: mine.headImageThumb, |
|||
isCamera: false, |
|||
isMicroPhone: true |
|||
}) |
|||
} |
|||
let rtcInfo = { |
|||
isHost: false, |
|||
groupId: this.groupId, |
|||
inviterId: mine.id, |
|||
userInfos: userInfos |
|||
} |
|||
// 通过home.vue打开多人视频窗口 |
|||
this.$eventBus.$emit("openGroupVideo", rtcInfo); |
|||
|
|||
}, |
|||
onCancel(){ |
|||
this.isShow = false; |
|||
let rtcInfo = { |
|||
isHost: false, |
|||
groupId: this.groupId, |
|||
inviterId: mine.id, |
|||
userInfos: userInfos |
|||
} |
|||
// 通过home.vue打开多人视频窗口 |
|||
this.$eventBus.$emit("openGroupVideo", rtcInfo); |
|||
|
|||
}, |
|||
onCancel() { |
|||
this.isShow = false; |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.rtc-group-join { |
|||
height: 260px; |
|||
.rtc-group-join { |
|||
height: 260px; |
|||
padding: 10px; |
|||
|
|||
.host-info { |
|||
display: flex; |
|||
flex-direction: column; |
|||
font-size: 16px; |
|||
padding: 10px; |
|||
.host-info { |
|||
display: flex; |
|||
flex-direction: column; |
|||
font-size: 16px; |
|||
padding: 10px; |
|||
height: 100px; |
|||
align-items: center; |
|||
height: 100px; |
|||
align-items: center; |
|||
|
|||
.host-text{ |
|||
margin-top: 5px; |
|||
} |
|||
.host-text { |
|||
margin-top: 5px; |
|||
} |
|||
} |
|||
|
|||
.users-info { |
|||
font-size: 16px; |
|||
margin-top: 20px; |
|||
|
|||
.users-info { |
|||
font-size: 16px; |
|||
margin-top: 20px; |
|||
.user-list { |
|||
display: flex; |
|||
padding: 5px 5px; |
|||
height: 90px; |
|||
flex-wrap: wrap; |
|||
justify-content: center; |
|||
.user-list { |
|||
display: flex; |
|||
padding: 5px 5px; |
|||
height: 90px; |
|||
flex-wrap: wrap; |
|||
justify-content: center; |
|||
|
|||
.user-item{ |
|||
padding: 2px; |
|||
} |
|||
.user-item { |
|||
padding: 2px; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</style> |
|||
@ -1,72 +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 |
|||
} |
|||
} |
|||
} |
|||
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 |
|||
} |
|||
} |
|||
} |
|||
}) |
|||
|
|||
|
Before Width: | Height: | Size: 132 KiB |
Loading…
Reference in new issue