<template> <div class="RongYun" id="RongYun"> <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="allData.userList.length > 0"> <template v-for="(item, index) in allData.userList" :key="index"> <div class="user" v-if="item.phone != userId"> <div class="userInfoClass"> <span class="username">{{ item.name }}:</span> <div class="ceninfoBox"> <div class="userphone" v-if="item.phone"> {{ geTel(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> <div class="user" v-else> <div class="userInfoClass"> <span class="username">现场用户</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>当前登录用户 ID:</span ><span id="userId" class="Rightvalue" @click="getToken()">{{ userId + "_web" }}</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: center; align-items: center; color: white; " @change="changeMediaType" > <el-radio :label="2">视频</el-radio> <el-radio :label="1">语音</el-radio> </el-radio-group> </div> </div> <div id="inviteList" class="textDiv"> <span class="dote"></span> <div class="text"> <span>选择邀请人:</span> <el-select v-model="Getidinvite" placeholder="选择被邀请人" filterable multiple collapse-tags > <el-option v-for="dict in inviteuserIdsList" :key="dict.registerThridUserId" :label="dict.name + '(' + dict.registerPlatform + ')'" :value="dict.registerThridUserId" ></el-option> </el-select> </div> </div> <div class="rong-login-box"> <div class="btn callBtn" id="call" @click="callHandler()"> <img src="/src/assets/images/rongYunImg/callic.png" alt="" /> 发起通话 </div> <div class="btn hunupBtn hangBtn" id="hunup" @click="hungup()"> <img src="/src/assets/images/rongYunImg/hangic.png" alt="" /> 挂断 </div> <div class="btn inviteBtn hunupBtn" id="invite" @click="inviteCall()">邀请</div> </div> </div> <div id="videoView" class="rong-video-box newbox"></div> </div> </div> </template> <script setup> import moment from "moment"; import useUserStore from "@/store/modules/user"; import { ElMessageBox } from "element-plus"; import bus from "@/utils/util"; import rongyunStore from "@/store/modules/rongyunStore"; import { listrcloudUnitUser, getUserStatus, rongyunGroupSave, groupJoinUsers, batchAdd, answer, hangUp, } from "@/views/RongyunCommunication/rongyunjs/rongyunApi.js"; import { listDept } from "@/api/system/dept.js"; import { findCurrentTreeObj } from "@/utils/util"; const appStore = useUserStore(); const useRongyunStore = rongyunStore(); const { proxy } = getCurrentInstance(); const inviteuserIdsList = ref([]); //被邀请人的列表 const Getidinvite = ref([]); const allData = reactive({ typeList: [ { label: "视频", value: 2, }, { label: "语音", value: 1, }, ], userId: appStore.userInfo.phonenumber, userName: "", mediaType: useRongyunStore.mediaType, userList: [], idList: [], userinfo: appStore.userInfo, unitArr: [], }); const { typeList, userId, userName, mediaType, userList, idList, userinfo, unitArr, } = toRefs(allData); const props = defineProps({ ryUser: Object, callType: Number, }); const paramSave = ref({ groupId: undefined, //群聊Id promoterThridUserId: appStore.userInfo.phonenumber + "_web", //发起人id answerThridUserIds: "", //接收人id type: "", //通话类型 1是视频 2是语音 createBy: localStorage.getItem("userNo"), }); onMounted(() => { nextTick(() => { initGroupData(); }); bus.on("removeEle", (e) => { removeVideoEl(); }); bus.on("ZhuDongBoHao", (e) => { removeVideoEl(); }); }); onBeforeUnmount(() => { close(); }); const geTel = (tel) => { var reg = /^(\d{3})\d{4}(\d{4})$/; return tel.replace(reg, "$1****$2"); }; // 处理群呼数据 const initGroupData = () => { console.log("props.ryUser 处理群呼/单呼数据", props.ryUser); // debugger; allData.userList = []; if (props.callType == 2) { let arr = [].concat(props.ryUser.userList); arr.forEach((v) => { allData.userList.push({ name: v.name, phone: v.registerThridUserId, typeFont: null, ClassType: null, }); }); } else { allData.userList.push({ name: props.ryUser.name, phone: props.ryUser.phone, typeFont: null, ClassType: null, }); } console.log("allData.userList", allData.userList); }; const getDom = (key) => { return document.querySelector(key); }; const changeMediaType = (value) => { console.log("value", 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} 已接听`); console.log("接听监听,", allData.userList, sender); allData.userList.forEach((element) => { if (element.phone == sender.userId) { element.typeFont = "已接听"; element.ClassType = 2; element.answerType = 4; acceptApi(element); } }); }, onHungup: (sender, reason) => { // 当有远端用户挂断, 该函数有 3 个参数: sender 是发送者,reason 是挂断原因,session 是通话实例。这时用户可以在 UI 层提示‘xxx已挂断’。 proxy.$modal.msgWarning(` ${sender.userId} 已挂断`); console.log("挂断监听,", allData.userList, sender); console.log("挂断监听挂断原因,", reason); allData.userList.forEach((element) => { if (element.phone == sender.userId) { element.typeFont = "已挂断"; element.answerType = reason; // 挂断原因转义成后端所需字段 // 1.未响铃 2.响铃未接听 3.响铃后挂断 4.已接听 // 响铃后挂断 // if (reason == 12) { // element.answerType = 3; // } // // 响铃未接听 // if (reason == 15) { // element.answerType = 2; // } // // 已接听 // if (reason == 13) { // element.answerType = 4; // } hungupApi(element); } }); // 群组中移除相应节点 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(); } }, onTrackReady: (track) => { // 当本端资源或远端资源已获取, 该函数有 2 个参数:track 是本端资源或远端资源, session 是通话实例。这时用户可以用拿到的 track 播放音频、视频。 setTimeout(() => { appendVideoEl(track); }, 1500); // 当本端资源或远端资源已获取, 该函数有 2 个参数:track 是本端资源或远端资源, session 是通话实例。这时用户可以用拿到的 track 播放音频、视频。 if (!track.isLocalTrack()) { proxy.$modal.msgWarning("通话已建立"); } }, onMemberModify: (sender, invitedUsers, session) => { console.log("触发邀请监听", sender, invitedUsers, session); console.log("触发邀请监听", allData.userList); // 群组通话中有其他人被邀请加入, 该函数有 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 = () => { console.log("..??", props); props.callType == 1 ? rongYunCall() : groupCall(); }; // 单呼 const call = () => { getDom("#call").style.display = "none"; getDom("#hunup").style.display = "flex"; const events = getCallSessionEvent(); console.log("props.ryUser", props.ryUser); console.log("props.ryUser.phone2", props.ryUser.phone); // 这里是传给融云拨打电话的传递userid const params = { targetId: props.ryUser.phone, mediaType: useRongyunStore.mediaType, listener: events, }; useRongyunStore.callClient.call(params).then(({ code, session }) => { if (code === 10000) { registerCallSessionEvent(session); useRongyunStore.SET_CALLSESSION(session); saveOnAccept(); } else { proxy.$modal.msgWarning(`呼叫失败,错误原因:${code}`); } }); }; //单呼前判登录用户在线状态 const rongYunCall = async (item) => { console.log("allData.userId", allData.userId, appStore.userInfo); let params = { registerThridUserId: allData.userId + "_web", }; let res1 = await getUserStatus(params); let flag1 = null; if (res1.code == 200) { flag1 = JSON.parse(res1.data); } 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 params = { registerThridUserId: props.ryUser.phone, }; let res = await getUserStatus(params); let flag = null; if (res.code == 200) { flag = JSON.parse(res.data); } if (flag != 1) { return proxy.$modal.msgWarning(`${props.ryUser.name}用户视频会商不在线!`); } else { call(); } }; //群呼 const groupCall = () => { getDom("#call").style.display = "none"; getDom("#hunup").style.display = "flex"; getDom("#invite").style.display = "flex"; getDom("#inviteList").style.visibility = "visible"; console.log("allData.userList", allData.userList, props.ryUser); allData.userList.forEach((v) => { v.typeFont = "已拨号"; v.ClassType = 0; v.answerType = 1; }); const events = getCallSessionEvent(); const params = { targetId: props.ryUser.groupId, userIds: props.ryUser.userIds.split(","), mediaType: useRongyunStore.mediaType, listener: events, }; console.log("param", params); useRongyunStore.callClient.callInGroup(params).then(({ code, session }) => { if (code === 10000) { registerCallSessionEvent(session); useRongyunStore.SET_CALLSESSION(session); saveOnAccept(); GetallOnline(); } else { const reason = code === RCCallErrorCode.NOT_IN_GROUP ? "当前用户未加入群组" : code; proxy.$modal.msgWarning(`呼叫失败,错误原因:${reason}`); removeVideoEl(); } }); }; // 邀请用户加入当前通话(仅限群组) const inviteCall = () => { if (Getidinvite.value && Getidinvite.value.length) { let params = { groupId: props.ryUser.groupId, ids: Getidinvite.value.join(","), }; groupJoinUsers(params).then((res) => { console.log(res); console.log("点击了邀请", Getidinvite.value); useRongyunStore.callSession.invite(Getidinvite.value).then(({ code }) => { console.log(code); if (code === 10000) { proxy.$modal.msgSuccess(`邀请成功`); } else { proxy.$modal.msgWarning(`邀请失败,错误原因:${code}`); } }); }); } else { proxy.$modal.msgWarning(`请选择邀请人`); } }; // 批量新增融合通讯通话 const saveOnAccept = () => { console.log("批量新增", props.ryUser); paramSave.value.type = useRongyunStore.mediaType; paramSave.value.groupId = props.callType == 1 ? -1 : props.ryUser.groupId; paramSave.value.answerThridUserIds = props.ryUser.phone || props.ryUser.userIds; //接听人单呼就是phone userIds 两个 数据不一样 console.log("paramSave.value", paramSave.value, props.ryUser); batchAdd(paramSave.value).then((res) => { if (res.code == 200) { console.log("批量新增融合通讯通话"); } }); }; // 接听后端接口 const acceptApi = (item) => { console.log("调取了接听接口", item); let params = { promoterThridUserId: appStore.userInfo.phonenumber + "_web", //发起人 当前登陆人 answerThridUserId: item.phone, //接听人 updateBy: localStorage.getItem("userNo"), //当前登录用户 }; answer(params).then((res) => { console.log("调取了接听接口", res); }); }; // 挂断融合通讯通话 const hungupApi = (item) => { console.log("调取了挂断接口", item); let params = { promoterThridUserId: appStore.userInfo.phonenumber + "_web", //发起人 answerThridUserId: item.phone, //接听人 updateBy: localStorage.getItem("userNo"), answerType: item.answerType || null, }; hangUp(params).then((res) => { console.log("调取了挂断接口", res); }); }; // 获取所有注册用户 const GetallOnline = (item) => { inviteuserIdsList.value = []; // let params = { // isOnlion: 1, // }; listrcloudUnitUser().then((res) => { res.data.map((item) => { item.userTokenList.map((p) => { p.name = item.name; // 过滤数据 保留当前在线并且不在已呼叫名单上的人 if (!props.ryUser.userIds.includes(p.registerThridUserId)) { if (p.isOnlion == 1 && p.registerPlatform == "web") { inviteuserIdsList.value.push(p); } else if (p.registerPlatform == "app") { inviteuserIdsList.value.push(p); } } }); }); console.log("被邀请人", inviteuserIdsList.value, props.ryUser.userIds); }); }; /** * video 视图渲染 */ const appendVideoEl = async (track) => { const uid = track.getUserId(); let params = { phone: uid.split("_")[0], }; let mediaType = useRongyunStore.mediaType; let data = await listrcloudUnitUser(params); console.log("datar", data); 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; if (mediaType == 1) { videoTpl = `<span class="video-user-id">${data.data[0].name} </span><span class="video-media-type"></span><video id="${uid}"></video>`; } else { videoTpl = `<span class="video-user-id">${data.data[0].name} </span><video id="${uid}"></video>`; } node.innerHTML = videoTpl; node.classList.add("video-item"); let length = allData.userList.length; console.log("什么东西", allData.userId, uid, props.callType, length); // 根据人数判断渲染不同的视图样式 if (allData.userId + "_web" == uid && length <= 2) { // 判断是当前用户 人数小于等于2人时给予单独样式 node.classList.add("first-child"); } 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 = () => { // console.log("1231------------------------", useRongyunStore.callSession); useRongyunStore.callSession.hungup().then(({ code }) => { if (code === 10000) { // proxy.$modal.msgWarning("挂断成功"); getDom("#call").style.display = "flex"; getDom("#hunup").style.display = "none"; getDom("#invite").style.display = "none"; getDom("#inviteList").style.visibility = "hidden"; removeVideoEl(); restore(); } else { proxy.$modal.msgWarning(`挂断失败,错误原因:${code}`); } allData.userList.forEach((element) => { element.typeFont = "已挂断"; element.ClassType = 3; element.answerType = code; hungupApi(element); }); }); }; const removeVideoEl = () => { getDom("#videoView").innerHTML = ""; getDom("#videoView").style = ""; getDom("#call").style.display = "flex"; getDom("#hunup").style.display = "none"; getDom("#invite").style.display = "none"; getDom("#inviteList").style.visibility = "hidden"; allData.idList = []; }; const close = () => { if (useRongyunStore.callSession != null) { hungup(); } restore(); }; // 输出组件的方法,让外部组件可以调用 defineExpose({ close, }); // 还原 const restore = () => { let style = { width: "934px" }; getDom("#rong-action").style.display = "block"; const dom = getDom("#RongYun"); const dom1 = getDom("#videoView"); getDom("#videoView").style = ""; 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; } }); }; </script> <style lang="scss"> @import "@/views/RongyunCommunication/rongyuncss/index.scss"; .rong-container { padding: 20px; display: flex; width: 100%; height: 500px; .rong-action { width: 360px; display: flex; flex-direction: column; margin-right: 20px; position: relative; #inviteList { visibility: hidden; } .textDiv { min-height: 38px; line-height: 38px; display: flex; align-items: center; margin-top: 10px; :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-family: Source Han Sans CN; font-weight: 500; font-size: 14px; color: #daf7ff; .el-radio__label { color: white; } } .Rightvalue { max-width: 160px; overflow: hidden; //溢出隐藏 text-overflow: ellipsis; //超出显示省略号 white-space: nowrap; //强制文本在一行内显示 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-family: Source Han Sans CN; font-weight: 500; font-size: 14px; color: #daf7ff; } .userList { max-height: 220px; 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: rgba(62, 230, 255, 0.1); align-items: center; border-radius: 1px; padding: 2px 8px; } .username { min-width: 52px; font-weight: 500; font-family: Source Han Sans CN; font-weight: 500; font-size: 14px; color: #2fd5ff; } .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: 100%; height: 40px; align-items: center; // margin-top: 50px; position: absolute; bottom: 20px; .btn { width: 100%; 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: center; align-items: center; color: #fff; margin: 0 auto; cursor: pointer; flex-direction: row; flex-wrap: nowrap; img { width: 18px; height: 18px; } } .callBtn { background: #67c23a; } .hangBtn { background: #ff3a5a; } .inviteBtn { background: #158bd2; } .hunupBtn { display: none; } .acceptBtn { display: none; } } } .rong-video-box { width: 390px; height: 100%; display: flex; flex-wrap: wrap; overflow: auto; position: relative; background-color: #154e63; background-image: url("@/assets/images/rongYunImg/qstp_img.png"); background-repeat: no-repeat; background-position: center center; .video-item { box-sizing: border-box; width: 100%; height: calc(100% - 10px); position: relative; .video-user-id { position: absolute; top: 10px; padding-left: 30px; 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-4 { width: 50%; height: calc(50% - 10px); } .video-item-8 { width: 25%; height: calc(50% - 10px); } .first-child { position: absolute; right: 0px; top: 0; width: 40%; height: 30%; z-index: 999; } } } </style> <style lang="scss" scoped> .RongYun { width: 100%; height: calc(100% - 40px); position: relative; .closeIcon { position: absolute; right: 0px; top: -30px; &:hover { color: chocolate; } } } </style>