<template> <div class="RongYun" id="RongYun"> <!-- <n-icon size="20" class="closeIcon" style="cursor: pointer; z-index: 1000" @click="close()" > <Close /> </n-icon> <n-icon size="20" class="fullScreen" style="cursor: pointer; z-index: 1000" @click="fullScreen()" v-if="fullFlag && trackFlag" > <ExpandOutline /> </n-icon> <n-icon size="20" class="fullScreen" style="cursor: pointer; z-index: 1000" @click="restore()" v-if="!fullFlag && trackFlag" > <Contract /> </n-icon> <n-icon size="20" class="rotateBtn" style="cursor: pointer; z-index: 1000" @click="rotate()" v-if="!fullFlag && userList.length == 1" > <Refresh /> </n-icon> --> <!-- <n-icon size="20" class="fullBigScreen" style="cursor: pointer; z-index: 1000" @click="bigFull()" v-if="bigFlag"> <AppsOutline /> </n-icon> --> <div class="rong-container" id="rongContainer"> <div class="rong-action" id="rong-action"> <div class="textDiv2"> <div class="textTitle"> <span class="dote"></span> <span>接收用户:</span> </div> <div class="userList" v-if="userList.length > 0"> <template v-for="(item, index) in userList" :key="index"> <div class="user" v-if="item.phone != userId"> <div class="userInfoClass"> <span class="username">{{ item.userName }}:</span> <div class="ceninfoBox"> <div class="userphone">{{ geTel(item.phone) }}</div> <!-- <div class="userphone">{{ item.phone }}</div> --> <div class="userphone userdept">{{ item.deptName }}</div> </div> </div> <div> <span style="margin-left: 10px" :class="'classType' + item.ClassType">{{ item.typeFont }}</span> </div> </div> </template> </div> </div> <div class="textDiv"> <span class="dote"></span> <div class="text"> <span>发起人:</span><span class="Rightvalue" @click="getToken()">{{ userinfo.userName }}</span> </div> </div> <div class="textDiv"> <span class="dote"></span> <div class="text"> <span>所在单位:</span><span class="Rightvalue" id="userId" v-if="unitArr && unitArr.length > 0">{{ getUnitName(userinfo.dept_id) || "" }}</span> </div> </div> <!-- <div class="textDiv"> <span class="dote"></span> <div class="text"> <span>当前登录用户 ID:</span ><span id="userId" class="Rightvalue" @click="getToken()">{{ userId }}</span> </div> </div> --> <div class="textDiv"> <span class="dote"></span> <div class="text"> <span>通话类型:</span> <el-radio-group v-model="mediaType" style=" display: flex; justify-content: space-around; align-items: center; width: 62%;" @input="changeMediaType"> <el-radio :label="2">视频</el-radio> <el-radio :label="1">语音</el-radio> </el-radio-group> </div> </div> <div class="rong-login-box"> <div class="btn callBtn" id="call" @click="callHandler()"> <img src="~@/assets/images/rongYunImg/callic.png" alt="" /> 发起通话 </div> <div class="btn hunupBtn hangBtn" id="hunup" @click="hungup()"> <img src="~@/assets/images/rongYunImg/hangic.png" alt="" /> 挂断 </div> <!-- <div class="btn acceptBtn" id='accept' @click="accept()">接听</div> --> </div> </div> <div id="videoView" class="rong-video-box"></div> <div class="fullHunupDiv" v-if="!fullFlag"> <img class="fullHunup" id="fullHunup" src="@/assets/images/rongYunImg/call-off.png" alt="" @click="hungup()" /> </div> </div> </div> </template> <script setup> import useAppStore from "@/store/modules/user"; import { ElMessageBox } from "element-plus"; import axios from "axios"; import bus from "@/utils"; const appStore = useAppStore(); import rongyunStore from "@/store/modules/rongyun"; const useRongyunStore = rongyunStore(); import { getUserStatus, queryUserData } from "@/api/rongyun/rongyunApi.js"; const { proxy } = getCurrentInstance(); import { installer as callInstaller, RCCallClient, RCCallSession, RCCallErrorCode, RCCallLanguage, RCCallEndReason, RCCallMediaType, RCCallSessionState, } from "@rongcloud/plugin-call"; // import { getList } from "@/api/system/dept"; // import { findCurrentTreeObj } from "@/util/util"; const allData = reactive({ typeList: [ { label: "视频", value: 2, }, { label: "语音", value: 1, }, ], userId: appStore.userInfo.phonenumber, userName: "", // mediaType: allData.mediaType, mediaType: 2, userList: [], fullFlag: true, trackFlag: false, rotateFlag: true, idList: [], userinfo: appStore.userInfo, unitArr: [], }); const { typeList, userId, userName, mediaType, userList, fullFlag, trackFlag, rotateFlag, idList, userinfo, unitArr, } = toRefs(allData); const props = defineProps({ ryUser: Object, callType: Number, }); onMounted(() => { console.log('appStore.userInfo', appStore.userInfo); console.log("ryUser", props.ryUser); // getUnitList(); nextTick(() => { // allData.restore(); initGroupData(); }); bus.on("removeEle", (e) => { removeVideoEl(); }); }); const geTel = (tel) => { if (tel) { var reg = /^(\d{3})\d{4}(\d{4})$/; return tel.replace(reg, "$1****$2"); } }; // 获取所有单位信息进行匹配当前用户单位 const getUnitList = () => { getList().then((res) => { console.log("res单位", res); if (res.data.code == 200) { allData.unitArr = res.data.data; } }); }; // 递归获取当前信息 const getUnitName = (id) => { let info = findCurrentTreeObj(allData.unitArr, "id", id); console.log("info", info); return info ? info.deptName : ""; }; // 处理群呼数据 const initGroupData = () => { allData.userList = []; if (props.callType == 2) { let arr = [].concat(props.ryUser.userList); arr.forEach((v) => { allData.userList.push({ userName: v.userName, phone: v.phone, deptName: v.deptName, typeFont: null, ClassType: null, }); }); } else { allData.userList.push({ userName: props.ryUser.userName, phone: props.ryUser.phone, deptName: props.ryUser.deptName, typeFont: null, ClassType: null, }); } console.log('allData.userList', allData.userList); }; const getDom = (key) => { return document.querySelector(key); }; const changeMediaType = (value) => { useRongyunStore.SET_MediaType(value); }; /** * CallSession 事件 */ const getCallSessionEvent = () => { return { onRinging: (sender) => { // 当远端用户已开始响铃, 该函数有 2 个参数: sender 是发送者,session 是通话实例。这时用户可以在业务层做响铃的 UI 展示。 // ; proxy.$modal.msgWarning(` ${sender.userId} 振铃`); allData.userList.forEach((element) => { if (element.phone == sender.userId) { element.typeFont = "对方已响铃"; element.ClassType = 1; } }); }, onAccept: (sender) => { // 当远端用户已同意接听, 该函数有 2 个参数: sender 是发送者,session 是通话实例。这时用户可以把 UI 的‘响铃’变成‘通话中’。 proxy.$modal.msgWarning(` ${sender.userId} 已接听`); allData.userList.forEach((element) => { if (element.phone == sender.userId) { element.typeFont = "已接听"; element.ClassType = 2; } }); }, onHungup: (sender) => { // 当有远端用户挂断, 该函数有 3 个参数: sender 是发送者,reason 是挂断原因,session 是通话实例。这时用户可以在 UI 层提示‘xxx已挂断’。 proxy.$modal.msgWarning(` ${sender.userId} 已挂断`); allData.userList.forEach((element) => { if (element.phone == sender.userId) { element.typeFont = "已挂断"; element.ClassType = 3; } // if (allData.userList.length <= 1) { // restore() // allData.trackFlag = false // } }); // 群组中移除相应节点 const videoViewDom = getDom("#videoView"); const videoDom = getDom(`#video-${sender.userId}`); videoDom && videoViewDom.removeChild(videoDom); const itemdomList = document.getElementsByClassName("video-item"); if (itemdomList.length <= 1) { restore(); allData.trackFlag = false; } }, onTrackReady: (track) => { // 当本端资源或远端资源已获取, 该函数有 2 个参数:track 是本端资源或远端资源, session 是通话实例。这时用户可以用拿到的 track 播放音频、视频。 setTimeout(() => { appendVideoEl(track); }, 1500); // 当本端资源或远端资源已获取, 该函数有 2 个参数:track 是本端资源或远端资源, session 是通话实例。这时用户可以用拿到的 track 播放音频、视频。 if (!track.isLocalTrack()) { proxy.$modal.msgWarning("通话已建立"); allData.trackFlag = true; } }, onMemberModify: (sender, invitedUsers) => { // 群组通话中有其他人被邀请加入, 该函数有 3 个参数: sender 是发送者,invitedUsers 是被邀请的用户列表, session 是通话实例。这时用户可以在 UI 层提示‘xxx加入通话’。 }, onMediaModify: (sender) => { // 通话类型改变时触发, 该函数有3个参数: sender 是发送者,mediaType 通话类型, session 是通话实例。这时用户可以在 UI 层提示‘已降级成音频通话’。 }, onAudioMuteChange: (muteUser) => { // 对方静音后触发, 该函数有2个参数: muteUser 是已静音的用户, session 是通话实例。这时用户可以在 UI 层提示‘xxx已静音’。 proxy.$modal.msgWarning(` ${sender.userId} 已静音`); }, onVideoMuteChange: (muteUser) => { // 对方禁用视频后触发, 该函数有2个参数: muteUser 是已禁用视频的用户, session 是通话实例。这时用户可以在 UI 层提示‘xxx已禁用视频’。 proxy.$modal.msgWarning(` ${sender.userId} 已禁用视频`); }, }; }; /** * callSession 事件注册 */ const registerCallSessionEvent = (session) => { const events = getCallSessionEvent(); session.registerSessionListener(events); }; const callHandler = () => { props.callType == 1 ? rongYunCall() : groupCall(); }; // 单呼 const call = () => { getDom("#call").style.display = "none"; getDom("#hunup").style.display = "block"; const events = getCallSessionEvent(); console.log(props.ryUser, "props.ryUser"); const params = { targetId: props.ryUser.phone, mediaType: allData.mediaType, listener: events, }; useRongyunStore.callClient.call(params).then(({ code, session }) => { if (code === 10000) { registerCallSessionEvent(session); useRongyunStore.SET_CALLSESSION(session); } else { proxy.$modal.msgWarning(`呼叫失败,错误原因:${code}`); } }); }; //单呼前判登录用户在线状态 const rongYunCall = async (item) => { let formData1 = new FormData(); formData1.append("userId", allData.userId); let res1 = await getUserStatus(formData1); let flag1 = null; if (res1.code == 200) { let data = JSON.parse(res1.msg); flag1 = data.status; } if (flag1 != 1) { ElMessageBox.confirm( `${appStore.userInfo.userName}用户视频会商不在线,是否重新连接?, "提示"`, { confirmButtonText: "确定", cancelButtonText: "取消", type: "warning", } ) .then(() => { setTimeout(() => { bus.emit("rystatus"); getCallStatus(); }, 1500); }) .catch(() => { }); } else { setTimeout(() => { getCallStatus(); }, 1500); } }; //单呼前判对方用户在线状态 const getCallStatus = async () => { let formData = new FormData(); formData.append("userId", props.ryUser.phone); let res = await getUserStatus(formData); let flag = null; if (res.code == 200) { let data = JSON.parse(res.msg); flag = data.status; } if (flag != 1) { return proxy.$modal.msgWarning( `${props.ryUser.userName}用户视频会商不在线!` ); } else { call(); } }; //群呼 const groupCall = () => { getDom("#call").style.display = "none"; getDom("#hunup").style.display = "block"; let userIDs = []; allData.userList.forEach((v) => { v.typeFont = "已拨号"; v.ClassType = 0; }); allData.userList.forEach((v) => { userIDs.push(v.phone); }); const events = getCallSessionEvent(); const params = { targetId: props.ryUser.groupId, userIds: userIDs, mediaType: allData.mediaType, listener: events, }; console.log('code', params); useRongyunStore.callClient.callInGroup(params).then(({ code, session }) => { console.log('code', code, session); if (code === 10000) { registerCallSessionEvent(session); useRongyunStore.SET_CALLSESSION(session); } else { const reason = code === RCCallErrorCode.NOT_IN_GROUP ? "当前用户未加入群组" : code; proxy.$modal.msgWarning(`呼叫失败,错误原因:${reason}`); removeVideoEl(); } }); }; /** * video 视图渲染 */ const appendVideoEl = async (track) => { const uid = track.getUserId(); let formData1 = new FormData(); formData1.append("userId", uid); let mediaType = allData.mediaType; let data = await queryUserData(formData1); if (data.code == 200) { const container = getDom("#videoView"); const node = document.createElement("div"); node.setAttribute("id", `video-${uid}`); if (allData.idList.findIndex((i) => i == `video-${uid}`) == -1) { allData.idList.push(`video-${uid}`); } let videoTpl = null; console.log('datas', data); if (mediaType == 1) { videoTpl = `<span class="video-user-id">${data.data.userName}: ${uid} </span><span class="video-media-type"></span><video id="${uid}"></video>`; } else { videoTpl = `<span class="video-user-id">${data.data.userName}: ${uid} </span><video id="${uid}"></video>`; } node.innerHTML = videoTpl; node.classList.add("video-item"); if (allData.userId == uid && props.callType == 1) { // 判断是当前用户 node.classList.add("video-item-current"); } let length = allData.userList.length; if (length > 2 && length <= 4) { node.classList.add("video-item-4"); } if (length > 4 && length <= 8) { node.classList.add("video-item-8"); } if (track.isAudioTrack()) { if (mediaType == 1) { container.appendChild(node); } track.play(); } else { container.appendChild(node); const videoEl = document.getElementById(`${track.getUserId()}`); track.play(videoEl); } } }; /** * 挂断当前 callSession */ const hungup = () => { useRongyunStore.callSession.hungup().then(({ code }) => { if (code === 10000) { proxy.$modal.msgWarning("挂断成功"); getDom("#call").style.display = "block"; getDom("#hunup").style.display = "none"; removeVideoEl(); allData.userList.forEach((v) => { v.typeFont = "已挂断"; v.ClassType = 3; }); restore(); allData.trackFlag = false; } else { proxy.$modal.msgWarning(`挂断失败,错误原因:${code}`); // getDom('#call').style.display = 'block' // getDom('#hunup').style.display = 'none' // getDom('#accept').style.display = 'none' } }); }; const removeVideoEl = () => { getDom("#videoView").innerHTML = ""; getDom("#videoView").style = ""; allData.rotateFlag = true; getDom("#call").style.display = "block"; getDom("#hunup").style.display = "none"; allData.idList = []; }; const close = () => { if (useRongyunStore.callSession != null) { hungup(); } restore(); // bus.emit("closeRY"); }; // 输出组件的方法,让外部组件可以调用 defineExpose({ close, }); // 视频旋转 const rotate = () => { const dom1 = getDom("#rongContainer"); const dom = getDom("#videoView"); // 小视频本人视角 const itemdom = getDom(".video-item-current"); if (allData.rotateFlag) { // 旋转主视频视角 dom.style.transform = `rotate(90deg)`; dom.style.transformOrigin = "top left"; //宽高互换 dom.style.width = dom1.offsetHeight + "px"; dom.style.height = dom1.offsetWidth - 60 + "px"; dom.style.position = "absolute"; dom.style.left = "98%"; // dom.style.marginLeft = "50%"; dom.style.marginLeft = "0"; dom.style.top = "0px"; // 小视频本人视角 const itemdom = getDom(".video-item-current"); if (itemdom) { itemdom.style.position = "absolute"; itemdom.style.bottom = "0"; itemdom.style.left = "0"; } allData.idList.forEach((item) => { // 用户名称信息 const userdom = getDom(`#${item} .video-user-id`); // userdom.style.transform = `rotate(270deg) translate(-50%, -50%)`; userdom.style.transform = `rotate(270deg)`; userdom.style.position = "absolute"; userdom.style.marginTop = "0"; userdom.style.marginLeft = "0"; userdom.style.left = "-45%"; userdom.style.top = "50%"; }); } else { // 旋转主视频视角 dom.style.transform = `rotate(0deg)`; dom.style.transformOrigin = "top left"; //宽高互换 dom.style.height = dom1.offsetHeight + "px"; dom.style.width = dom1.offsetWidth + "px"; dom.style.position = "relative"; dom.style.left = "0"; dom.style.marginLeft = "0"; dom.style.top = "0"; itemdom.style = null; if (itemdom) { itemdom.style.position = "absolute"; itemdom.style.top = "0px"; itemdom.style.right = "0px"; } allData.idList.forEach((item) => { // 用户名称信息 const userdom = getDom(`#${item} .video-user-id`); userdom.style.transform = `rotate(0deg) translate(-50%, -50%)`; userdom.style.position = "absolute"; userdom.style.marginTop = "4%"; userdom.style.marginLeft = "50%"; userdom.style.left = "0"; userdom.style.top = "0"; }); } allData.rotateFlag = !allData.rotateFlag; }; // 全屏 const fullScreen = () => { allData.fullFlag = false; let style = { width: "100%", height: "100vh" }; allData.bus.$emit("fullScreen", style); getDom("#rong-action").style.display = "none"; const dom = getDom("#RongYun"); const dom1 = getDom("#videoView"); if (allData.rotateFlag) { dom1.style.width = "100%"; dom1.style.height = "100%"; dom1.style.left = "0"; dom1.style.marginLeft = "0"; } dom.classList.add("fullRongYun"); }; // 还原 const restore = () => { allData.fullFlag = true; let style = { width: "934px" }; bus.emit("fullScreen", style); getDom("#rong-action").style.display = "block"; const dom = getDom("#RongYun"); const dom1 = getDom("#videoView"); getDom("#videoView").style = ""; allData.rotateFlag = true; console.log('allData.idList', allData.idList); allData.idList.forEach((item, index) => { let videodom = getDom(`#${item}`); if (videodom) { videodom.style = null; } const titledom = getDom(`#${item} .video-user-id`); if (titledom) { titledom.style = null; } }); dom.classList.remove("fullRongYun"); }; </script> <style lang="scss"> .video-item { box-sizing: border-box; width: 100%; height: calc(100% - 10px); border: 1px solid #ccc; position: relative; .video-user-id { position: absolute; top: 5px; padding: 0 5px; border-radius: 2px; z-index: 100; font-size: 14px; color: #58fdff; display: inline-block; width: 100%; text-align: left; } .video-media-type { position: absolute; top: 30px; left: 0; display: block; width: 100%; height: calc(100% - 30px); background: url("~@/assets/images/rongYunImg/yuyin_bg.png") no-repeat; background-size: 100% 100%; } video { width: 100%; height: 100%; } } .video-item-current { position: absolute; top: 0; right: 0; width: 252px; height: 190px; z-index: 10; } </style> <style lang="scss" scoped> // @import "./css/style.css"; .RongYun { // width: 854px; // height: 467px; width: 100%; height: 100%; position: relative; .closeIcon { position: absolute; right: 0px; top: -30px; &:hover { color: chocolate; } } .fullScreen { position: absolute; right: 30px; top: -30px; &:hover { color: chocolate; } } .fullBigScreen { position: absolute; right: 60px; top: -30px; &:hover { color: chocolate; } } #LoadingDiv { position: absolute; left: 0; top: 0; width: 100%; height: 100%; text-align: center; line-height: 605px; } .classType0 { // 已呼叫 color: #0f93e4; } .classType1 { color: rgb(251, 255, 0); // 已响铃 } .classType2 { // 已接通 color: #3affce; color: #3affce; padding: 1px 3px; border: 1px solid #3affce; } .classType3 { // 已挂断 color: #ff3a5a; padding: 1px 3px; border: 1px solid #ff3a5a; } .rong-container { padding: 0 20px; display: flex; .rong-action { width: 35%; display: flex; flex-direction: column; margin-right: 20px; .textDiv { min-height: 38px; line-height: 38px; display: flex; align-items: center; :deep(.el-radio) { color: #fff; } .dote { display: block; margin-right: 20px; width: 10px; height: 10px; background: #58fdff; border-radius: 10px; } .text { display: flex; flex: 1; justify-content: space-between; font-size: 16px; font-family: Alibaba PuHuiTi; font-weight: 500; color: #81b6d4; } .Rightvalue { color: #fff; } } .textDiv2 { // min-height: 45px; // line-height: 45px; .dote { display: block; margin-right: 20px; width: 10px; height: 10px; background: #58fdff; border-radius: 10px; } .textTitle { height: 40px; line-height: 40px; display: flex; align-items: center; font-size: 16px; font-weight: 500; color: #81b6d4; } .userList { max-height: 200px; overflow-y: auto; .user { // background-color: #14306a; // padding: 0 10px; // margin-left: 20px; // border-bottom: 1px solid #0a0a0a; display: flex; justify-content: space-between; background: #093a73; align-items: center; border-radius: 1px; padding: 2px 8px; } .username { min-width: 52px; font-weight: 500; color: #13bfff; } .userphone { font-weight: bold; color: #ffffff; } .userInfoClass { display: flex; align-items: center; } .ceninfoBox { padding-left: 5px; .userdept { max-width: 140px; font-size: 12px; text-align: center; } } } } .rong-login-box { // margin-left: 20px; display: flex; width: 300px; height: 40px; align-items: center; margin-top: 50px; .btn { margin-right: 20px; background: #14306a; border-radius: 4px; padding: 6px 10px; font-size: 16px; font-family: Alibaba PuHuiTi; font-weight: 400; display: flex; justify-content: space-around; align-items: center; color: #fff; margin: 0 auto; cursor: pointer; img { width: 18px; height: 18px; } } .callBtn { background: #67c23a; } .hangBtn { background: #ff3a5a; } .hunupBtn { display: none; } .acceptBtn { display: none; } } } .rong-video-box { width: 65%; height: 100%; // width: 540px; // height: 460px; display: flex; flex-wrap: wrap; overflow: auto; .video-item { box-sizing: border-box; width: 252px; height: 194px; border: 1px solid #ccc; position: relative; .video-user-id { position: absolute; top: 5px; padding: 0 5px; border-radius: 2px; z-index: 100; font-size: 14px; color: #58fdff; display: inline-block; width: 100%; text-align: center; } .video-media-type { position: absolute; top: 30px; left: 0; display: block; width: 100%; height: calc(100% - 30px); background: url("~@/assets/images/rongYunImg/yuyin_bg.png") no-repeat; background-size: 100% 100%; } video { width: 250px; height: 194px; } } } .fullHunupDiv { position: absolute; bottom: 30px; width: 100%; display: flex; justify-content: center; align-items: center; img { width: 50px; height: 50px; } } } } .fullRongYun { width: 100%; height: calc(100vh - 150px); position: relative; .closeIcon { position: absolute; right: 30px; top: -30px; &:hover { color: chocolate; } } .fullScreen { position: absolute; right: 60px; top: -30px; &:hover { color: chocolate; } } .rotateBtn { position: absolute; right: 87px; top: -30px; &:hover { color: chocolate; } } } </style> <style lang="scss"> .rong-container { padding: 0 30px; width: 100%; height: calc(100% - 10px); .rong-video-box { width: 100%; height: 100%; position: relative; .video-item-4 { width: 50%; height: calc(50% - 10px); } .video-item-8 { width: 25%; height: calc(50% - 10px); } } } </style>