| | |
| | | <template> |
| | | <body><div id="meet" /></body> |
| | | <div> |
| | | <div style="display: flex; flex-direction: row;"> |
| | | <div id="meet" ref="meet" style="flex-grow: 1;"> |
| | | </div> |
| | | <div style="padding-top: 5px; padding-left: 5px;right: 15px"> |
| | | <el-button type="success" size="small" @click="hiddenStudent">{{ getShowText() }}</el-button> |
| | | <el-row v-show="showStudent"> |
| | | <el-row :gutter="5"> |
| | | <el-col :span="12"> |
| | | <el-input placeholder="搜索学员" size="small" clearable @input="getStudentList" @clear="getStudentList" |
| | | v-model="searchForm.keyword"/> |
| | | </el-col> |
| | | <el-col :span="2"> |
| | | <el-button type="primary" size="small" @click="getStudentList">搜索</el-button> |
| | | </el-col> |
| | | </el-row> |
| | | <el-tabs v-model="searchForm.onlineStatus" @tab-click="handleTabChange" style="margin-left: 3px"> |
| | | <el-tab-pane label="在线学员" name="1"></el-tab-pane> |
| | | <el-tab-pane label="离线学员" name="0"></el-tab-pane> |
| | | </el-tabs> |
| | | <el-table :data="showStudentList" style="width: 100%"> |
| | | <el-table-column prop="realName" label="学员姓名"></el-table-column> |
| | | <el-table-column prop="id" label="操作" width="80px;"> |
| | | <template slot-scope="scope"> |
| | | <el-dropdown trigger="click" @command="handleCommand"> |
| | | <i class="el-icon-more-outline" id="more"></i> |
| | | <el-dropdown-menu slot="dropdown"> |
| | | <el-dropdown-item :command="{ command: 'openCamera', id: scope.row.id }">打开/关闭摄像头</el-dropdown-item> |
| | | <el-dropdown-item :command="{ command: 'mute', id: scope.row.id }">静音/取消静音</el-dropdown-item> |
| | | <el-dropdown-item :command="{ command: 'kickOut', id: scope.row.id }">踢出</el-dropdown-item> |
| | | </el-dropdown-menu> |
| | | </el-dropdown> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="onlineStatus" label="状态" width="80px;"> |
| | | <template slot-scope="scope"> |
| | | <div :class="{ online: scope.row.onlineStatus === 1, outline: scope.row.onlineStatus === 0 }"> |
| | | {{ getStatus(scope.row.onlineStatus) }} |
| | | </div> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <el-button class="link-left" type="primary" size="small" @click="muteEveryone">全体静音</el-button> |
| | | <el-button class="link-left" type="primary" size="small" @click="videoEveryone">全体关闭摄像头</el-button> |
| | | </el-row> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script src='https://ycl.easyblog.vip:8443/external_api.js'></script> |
| | | <script> |
| | | let api |
| | | const initIframeAPI = () => { |
| | | const domain = 'ycl.easyblog.vip:8443' |
| | | const options = { |
| | | roomName: 'test', |
| | | width: 700, |
| | | height: 700, |
| | | parentNode: document.querySelector('#meet') |
| | | import { getStudentList } from '@/api/meet' |
| | | import Cookies from 'js-cookie' |
| | | |
| | | export default { |
| | | data () { |
| | | return { |
| | | ws: null, |
| | | jitsiApi: null, |
| | | width: 0, |
| | | height: 0, |
| | | showStudent: true, |
| | | intervalId: null, |
| | | meetId: null, |
| | | roomName: '', |
| | | joinList: [], |
| | | searchForm: { |
| | | keyword: '', |
| | | // 0 未在线、 1 在线 |
| | | onlineStatus: 0 |
| | | }, |
| | | studentList: [], |
| | | showStudentList: [] |
| | | } |
| | | }, |
| | | beforeDestroy () { |
| | | if (this.ws) { |
| | | this.ws.close() |
| | | } |
| | | }, |
| | | methods: { |
| | | muteEveryone () { |
| | | this.jitsiApi.executeCommand('muteEveryone', 'audio') |
| | | const h = this.$createElement; |
| | | |
| | | this.$notify({ |
| | | title: '提示', |
| | | message: h('i', { style: 'color: teal'}, '已全体禁音') |
| | | }); |
| | | }, |
| | | videoEveryone () { |
| | | this.jitsiApi.executeCommand('muteEveryone', 'video') |
| | | const h = this.$createElement; |
| | | |
| | | this.$notify({ |
| | | title: '提示', |
| | | message: h('i', { style: 'color: teal'}, '已关闭全体视频') |
| | | }); |
| | | }, |
| | | handleCommand (command) { |
| | | this.sendMessage(JSON.stringify(command)) |
| | | }, |
| | | initWebSocket () { |
| | | this.ws = new WebSocket('wss://www.kgmeet.com:18080/websocket/' + JSON.parse(Cookies.get('adminUserInfo')).id) |
| | | let ws = this.ws |
| | | ws.onopen = () => { |
| | | console.log('WebSocket 连接成功') |
| | | // 发送心跳数据 |
| | | ws.send('ping') |
| | | } |
| | | ws.onmessage = (event) => { |
| | | console.log('收到服务器消息:', event.data) |
| | | // 处理服务器发来的消息 |
| | | } |
| | | ws.onerror = (error) => { |
| | | console.error('WebSocket 连接出错:', error) |
| | | } |
| | | ws.onclose = () => { |
| | | console.log('WebSocket 连接已关闭') |
| | | // 可以在这里尝试重新连接 |
| | | } |
| | | |
| | | // 组件销毁时断开 WebSocket 连接 |
| | | this.$once('hook:beforeDestroy', () => { |
| | | ws.close() |
| | | }) |
| | | }, |
| | | sendMessage (message) { |
| | | if (this.ws.readyState === WebSocket.OPEN) { |
| | | this.ws.send(message) |
| | | } else { |
| | | console.error('WebSocket 连接未打开') |
| | | } |
| | | }, |
| | | hiddenStudent () { |
| | | this.showStudent = !this.showStudent |
| | | }, |
| | | changeJitsiWindowSize (width, height) { |
| | | this.jitsiApi.resizeLargeVideo(width, height) |
| | | }, |
| | | getShowText () { |
| | | if (this.showStudent) { |
| | | return '隐藏' |
| | | } else { |
| | | return '显示' |
| | | } |
| | | }, |
| | | getStatus (status) { |
| | | if (status === 1) { |
| | | return '在线' |
| | | } else if (status === 0) { |
| | | return '离线' |
| | | } |
| | | }, |
| | | handleTabChange (tab) { |
| | | let status = parseInt(tab.name) |
| | | this.showStudentList = this.studentList.filter(student => { |
| | | return student.onlineStatus === status |
| | | }) |
| | | }, |
| | | getStudentList () { |
| | | let params = { |
| | | keyword: this.searchForm.keyword |
| | | } |
| | | getStudentList(this.meetId, params).then(res => { |
| | | this.studentList = res.data.data |
| | | this.showStudentList = this.studentList.filter(student => { |
| | | return student.onlineStatus === this.searchForm.onlineStatus |
| | | }) |
| | | }) |
| | | }, |
| | | getRoomInfo () { |
| | | this.jitsiApi.getRoomsInfo().then(rooms => { |
| | | rooms.rooms.forEach(room => { |
| | | // 房间的id是一个子域名,且@符前的会议名称是经过URL编码的 |
| | | let encodedPart = room.id.split('@')[0] |
| | | let decodedPart = decodeURIComponent(encodedPart) |
| | | if (this.roomName === decodedPart) { |
| | | room.participants.forEach(user => { |
| | | // 使用'_'作为分隔符分割字符串,获取到userId |
| | | const parts = user.displayName.split('_') |
| | | let userId = null |
| | | if (parts.length > 1) { |
| | | userId = parseInt(parts[1]) |
| | | // 设置学员状态为在线 |
| | | this.studentList.forEach(student => { |
| | | console.log(student.id === userId) |
| | | if (student.id === userId) { |
| | | student.onlineStatus = 1 |
| | | } |
| | | }) |
| | | } |
| | | }) |
| | | } |
| | | }) |
| | | this.showStudentList = this.studentList.filter(student => { |
| | | return student.onlineStatus === parseInt(this.searchForm.onlineStatus) |
| | | }) |
| | | }) |
| | | } |
| | | }, |
| | | mounted () { |
| | | this.height = window.innerHeight |
| | | this.meetId = this.$route.query.meetId |
| | | this.getStudentList() |
| | | const domain = this.$route.query.domain |
| | | const roomName = this.$route.query.roomName |
| | | this.roomName = roomName |
| | | const userInfoStr = this.$route.query.userInfoStr |
| | | const userInfo = userInfoStr ? JSON.parse(userInfoStr) : null |
| | | const options = { |
| | | roomName: roomName, |
| | | height: this.height, |
| | | parentNode: this.$refs.meet, |
| | | lang: 'zh_CN', |
| | | userInfo: userInfo, |
| | | configOverwrite: { |
| | | prejoinConfig: { |
| | | enabled: false |
| | | }, |
| | | // 禁用邀请 |
| | | disableInviteFunctions: true, |
| | | // 禁用邮箱 |
| | | gravatar: { |
| | | disabled: true |
| | | }, |
| | | // 禁用改名 |
| | | readOnlyName: true, |
| | | // 自定义按钮 |
| | | toolbarButtons: [ |
| | | // 摄像头 |
| | | 'camera', |
| | | // 聊天 |
| | | 'chat', |
| | | // 共享 |
| | | 'desktop', |
| | | 'download', |
| | | 'fullscreen', |
| | | 'hangup', |
| | | // 'help', |
| | | 'highlight', |
| | | // 'invite', |
| | | 'linktosalesforce', |
| | | 'livestreaming', |
| | | 'microphone', |
| | | 'noisesuppression', |
| | | 'recording', |
| | | 'select-background', |
| | | 'settings', |
| | | 'shareaudio', |
| | | 'sharedvideo', |
| | | 'shortcuts', |
| | | 'stats', |
| | | 'tileview', |
| | | 'toggle-camera', |
| | | 'whiteboard', |
| | | |
| | | // 'closedcaptions', |
| | | // 'embedmeeting', |
| | | // 'etherpad', |
| | | // 'feedback', |
| | | // 'filmstrip', |
| | | 'participants-pane', |
| | | // 'profile', |
| | | 'raisehand', |
| | | // 'security', |
| | | 'videoquality', |
| | | ], |
| | | whiteboard: { |
| | | enabled: true |
| | | } |
| | | } |
| | | } |
| | | |
| | | this.jitsiApi = new window.JitsiMeetExternalAPI(domain, options) |
| | | |
| | | this.jitsiApi.addListener('readyToClose', () => { |
| | | window.close() |
| | | }) |
| | | // 初始化 |
| | | this.initWebSocket() |
| | | // 每三秒更学员在线状态 |
| | | this.intervalId = setInterval(() => { |
| | | this.getRoomInfo() |
| | | // 发送心跳数据 |
| | | this.ws.send('ping') |
| | | }, 2500) |
| | | |
| | | }, |
| | | beforeDestroy () { |
| | | // 清除定时器,避免内存泄漏 |
| | | if (this.intervalId) { |
| | | clearInterval(this.intervalId) |
| | | this.intervalId = null |
| | | } |
| | | } |
| | | api = new JitsiMeetExternalAPI(domain, options) |
| | | } |
| | | |
| | | window.onload = () => { |
| | | initIframeAPI() |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped></style> |
| | | <style lang="scss" scoped> |
| | | /deep/ thead { |
| | | display: none; |
| | | } |
| | | |
| | | #more:hover { |
| | | cursor: pointer; |
| | | } |
| | | |
| | | #meet { |
| | | height: 100%; |
| | | } |
| | | |
| | | .online { |
| | | color: #42b983; |
| | | } |
| | | |
| | | .outline { |
| | | color: #aa1111; |
| | | } |
| | | |
| | | .studentWarp { |
| | | display: flex; |
| | | flex-direction: row; |
| | | } |
| | | |
| | | .student-row { |
| | | margin-top: 8px; |
| | | padding-left: 3px; |
| | | color: #565b5e; |
| | | } |
| | | </style> |