luohairen
2024-11-07 d0d825e09ffdb95e9c4edcc44eeff08c4b2a9c23
src/views/meet/index.vue
@@ -1,22 +1,217 @@
<template>
  <div id="meet" ref="meet">
  <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>
let jitsiApi = null
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 () {
    const width = window.innerWidth
    const height = window.innerHeight
    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,
      width: width,
      height: height,
      height: this.height,
      parentNode: this.$refs.meet,
      lang: 'zh_CN',
      userInfo: userInfo,
@@ -24,25 +219,115 @@
        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
        }
      },
      toolbarButtons: ['whiteboard']
      }
    }
    jitsiApi = new window.JitsiMeetExternalAPI(domain, options)
    jitsiApi.addListener('readyToClose', () => {
    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
    }
  }
}
</script>
<style lang="scss" scoped>
/deep/ thead {
  display: none;
}
#more:hover {
  cursor: pointer;
}
#meet {
  width: 100%;
  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>