WGShare.Client.Electron/src/page/Meeting/index.tsx

1139 lines
49 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import styles from '@/page/Meeting/index.module.scss'
import { useEffect, useRef, useState } from "react";
import Operation from '@/components/Operation';
import SpeakerModeModal from '@/components/SpeakerModeModal';
import InvitingPersonnelModal from '@/components/InvitingPersonnelModal';
import { Button, Input, Popover, Modal, Checkbox, message } from "antd";
import { SearchOutlined } from '@ant-design/icons';
import { useLocation, useNavigate } from 'react-router-dom';
import { thumbImageBufferToBase64 } from '@/utils/package/base64'
import { storage } from '@/utils';
import { GetRoomUser, PostOpenMicr, PostOpenCamera, PostRoomManager, DeleteRoomManager, GetRoomKickout, GetShowUser, PostShowUser } from '@/api/Meeting';
import ImageUrl from '@/utils/package/ImageUrl'
import agora from '@/utils/package/agora'
import { onInvoke, onSignalr, offSignalr, onStart } from '@/utils/package/signalr';
import dayjs from 'dayjs';
import durationPlugin from 'dayjs/plugin/duration';
import { VideoSourceType } from 'agora-electron-sdk';
import { GetUserList } from '@/api/Home/User';
import Avatar from '@/components/Avatar';
import SharedFilesModel from '@/components/SharedFilesModel';
import StupWizard from '@/components/StupWizard';
const { exec } = require('child_process');
const fs = require('fs').promises;
dayjs.extend(durationPlugin);
const Meeting: React.FC = () => {
const navigate = useNavigate();
const { state } = useLocation();
const speakerModeModalRef = useRef<any>();
const sharedFilesModelRef = useRef<any>();
const invitingPersonnelRef = useRef<any>();
const stupWizardRef = useRef<any>();
const [statusList, setStatusList] = useState({
userList: false,
userChatList: false,
})
const [isSharedScreenModal, setIsSharedScreenModal] = useState(false);
const [user, setUser] = useState<any>({});
const [sharedScreenList, setSharedScreenList] = useState<any>([]);
const [sharedScreenItem, setSharedScreenItem] = useState<any>('');
const [textMsg, setTextMsg] = useState('');
const [footerList, setFooterList] = useState([
[
{
title: '关闭声音',
icon: ImageUrl.icon22,
iconActive: ImageUrl.icon22Active,
active: false,
},
{
title: '关闭视频',
icon: ImageUrl.icon23,
iconActive: ImageUrl.icon23Active,
active: false,
},
],
[
{
title: '共享屏幕',
icon: ImageUrl.icon24,
active: false,
},
{
title: '共享文件',
icon: ImageUrl.icon25,
active: false,
},
{
title: '邀请人员',
icon: ImageUrl.icon26,
active: false,
},
{
title: '录制',
icon: ImageUrl.icon27,
iconActive: ImageUrl.icon27Active,
active: false,
},
// {
// title: '设置向导',
// icon: ImageUrl.icon28,
// active: false,
// },
{
title: '结束',
icon: ImageUrl.icon29,
active: false,
},
],
[
{
title: '成员列表',
icon: ImageUrl.icon30,
active: false,
},
{
title: '聊天',
icon: ImageUrl.icon31,
active: false,
},
],
])
const [footerListIndex, setFooterListIndex] = useState<any>({
itemIndex: 0,
rowIndex: 0,
});
const [roomUserList, setRoomUserList] = useState<any>([])
const [allUserList, setAllUserList] = useState<any>([])
const [chatList, setChatList] = useState<any>([])
const [currentVideoId, setCurrentVideoId] = useState('')
let [currentSeconds, setCurrentSeconds] = useState(0)
const [currentEffective, setCurrentEffective] = useState(0)
const [isComputerAudio, setIsComputerAudio] = useState(true)
const [isFluencyPriority, setIsFluencyPriority] = useState(true)
const [open, setOpen] = useState(false)
const [modeOpen, setModeOpen] = useState(false)
const [meetingMode, setMeetingMode] = useState('')
const [userSearchValue, setUserSearchValue] = useState('')
const [noViewChatList, setNoViewChatList] = useState(0)
const [currentLookUserAccount, setCurrentLookUserAccount] = useState<any>('')
const [recorder, setRecorder] = useState<any>('')
const [mediaStream, setMediaStream] = useState<any>('')
const [currentLookUserStatus, setCurrentLookUserStatus] = useState<1 | 2 | 3 | 4>(1)
let userInfo = JSON.parse(storage.getItem('user') as string)
let allUserListArr = [] as any;
let currentLookUserAccountId = '' as string;
useEffect(() => {
let time = null as any;
setUser(userInfo)
setMeetingMode('StandardMode');
agora.init(true)
agora.registerEventHandler({
onJoinChannelSuccess: async (info: any, _elapsed: any) => {
if (String(info.localUid).length !== 9) {
await onInvoke('joinChannel', {
roomNum: info.channelId,
enableMicr: true,
enableCamera: true
})
await getRoomUser()
setTimeout(async () => {
let view = document.getElementById(`video-${info.localUid}`) as HTMLElement;
if (view && !view.getAttribute('load')) {
await agora.setupLocalVideo({
uid: Number(info.localUid),
view,
channelId: info.channelId,
sourceType: VideoSourceType.VideoSourceCameraPrimary,
})
view.setAttribute('load', 'true')
}
getShowUser();
}, 1000);
}
},
onUserJoined: async (info: any, remoteUid: any, _elapsed: any) => {
if (String(remoteUid).length !== 9) {
await getRoomUser()
setTimeout(async () => {
let view = document.getElementById(`video-${remoteUid}`) as HTMLElement;
if (view && !view.getAttribute('load')) {
await agora.setupRemoteVideoJoin({
uid: Number(remoteUid),
view,
channelId: info.channelId,
})
view.setAttribute('load', 'true')
}
}, 1000);
}
},
onUserOffline: async (info: any, remoteUid: any, _reason: any) => {
await agora.setupRemoteVideo({
uid: Number(remoteUid),
view: null,
channelId: info.channelId,
})
setTimeout(() => {
getRoomUser()
}, 1000);
}
})
agora.startCameraCapture()
agora.setJoinChannel({
channelId: state.channelId,
uid: userInfo.uid,
token: state.token,
})
storage.setItem('noViewChatList', 0)
window.addEventListener('customStorageChange', handleCustomStorageChange);
window.addEventListener('online', handleNetworkChange);
window.addEventListener('offline', handleNetworkChange);
time = setInterval(() => {
setCurrentSeconds(currentSeconds++)
}, 1000)
return () => {
window.removeEventListener('customStorageChange', handleCustomStorageChange);
window.removeEventListener('online', handleNetworkChange);
window.removeEventListener('offline', handleNetworkChange);
clearInterval(time)
};
}, []);
useEffect(() => {
roomUserList.forEach((item: any) => {
if (item.uid === user.uid) {
const footerListTemplate = [...footerList]
footerListTemplate[0][0].title = item.enableMicr ? '关闭声音' : '开启声音'
footerListTemplate[0][0].active = !item.enableMicr
footerListTemplate[0][1].title = item.enableCamera ? '关闭视频' : '开启视频'
footerListTemplate[0][1].active = !item.enableCamera
setFooterList(footerListTemplate)
}
agora.muteLocalAudioStream(!item.enableMicr)
agora.muteLocalVideoStream(!item.enableCamera)
});
}, [roomUserList]);
useEffect(() => {
const connection = (navigator as any).connection
if (connection.downlink === 0) {
setCurrentEffective(0)
} else {
let effectiveTypeLength = ['slow-2g', '2g', '3g', '4g'].indexOf((navigator as any).connection.effectiveType)
setCurrentEffective(effectiveTypeLength + 1)
}
}, [(navigator as any).connection.effectiveType]);
useEffect(() => {
onSignalr((item: any) => {
switch (item.key) {
case 'ReceiveMessage':
let meetingUserChatDom = document.getElementById('meetingUserChat') as HTMLElement;
if (!meetingUserChatDom) {
let storageNoViewChatList = Number(storage.getItem('noViewChatList'))
storage.setItem('noViewChatList', storageNoViewChatList += 1)
setNoViewChatList(storageNoViewChatList)
}
setChatList((newChatList: any) => [...newChatList, item])
break;
case 'RefreshUserList':
getRoomUser()
break;
case 'Operation':
// 1:全员退出会议
// 2:设置取消管理员
// 3:踢出房间
switch (item.type) {
case 1:
leaveChannel()
break;
case 2:
case 3:
getRoomUser()
break;
}
break;
case 'ForceExitRoom':
leaveChannel()
break;
case 'RefreshView':
setMeetingMode(item.type)
break;
case 'ShowUser':
getShowUser()
break;
}
})
return () => {
offSignalr()
}
}, [])
useEffect(() => {
if (recorder) {
const setting = JSON.parse(storage.getItem('setting') as string)
recorder.start();
recorder.ondataavailable = (event: any) => {
const blob = new Blob([event.data], {
type: 'video/mp4',
});
const reader = new FileReader() as any;
reader.onload = async () => {
const buffer = Buffer.from(reader.result);
await fs.writeFile(`${setting.recordingFilesPath}${+new Date()}.mp4`, buffer, {});
message.success(`录制成功!文件已保存至:${setting.recordingFilesPath}`)
try {
await fs.access(setting.recordingFilesPath, fs.constants.F_OK);
if (process.platform === 'win32') {
exec(`explorer "${setting.recordingFilesPath}"`);
} else if (process.platform === 'darwin') {
exec(`open "${setting.recordingFilesPath}"`);
}
} catch (error: any) {
if (error.code === 'ENOENT') {
message.error('文件夹不存在!')
} else {
message.error(error)
}
}
};
reader.readAsArrayBuffer(blob);
}
};
}, [recorder])
// 网络
const handleNetworkChange = (): void => {
if (navigator.onLine) {
message.success('网络已恢复。')
setTimeout(async () => {
await onStart(async () => {
await onInvoke('joinChannel', {
roomNum: state.channelId,
enableMicr: true,
enableCamera: true
})
await getRoomUser()
})
}, 1000)
} else {
message.error('网络已断开!')
}
}
// 全员观看
const getShowUser = async (): Promise<void> => {
await GetShowUser(state.channelId).then(async (res) => {
if (res.code === 200 && res.data) {
currentLookUserAccountId = res.data
let userItem = allUserListArr.find((item: any) => item.uid === res.data || item.screenShareId === res.data)
if (userItem) {
setCurrentLookUserAccount(userItem)
}
if (res.data === userInfo.uid || res.data === userInfo.screenShareId) {
if (String(res.data).length === 9) {
// 共享屏幕
setCurrentLookUserStatus(2)
setCurrentVideoId('2')
setTimeout(() => {
agora.setupLocalVideo({
uid: Number(res.data),
view: document.getElementById(`video-source-screen`) as HTMLElement,
channelId: state.channelId,
sourceType: VideoSourceType.VideoSourceScreen,
})
}, 1000);
} else {
setCurrentLookUserStatus(1)
setCurrentVideoId('1')
// 摄像头
setTimeout(() => {
agora.setupLocalVideo({
uid: Number(res.data),
view: document.getElementById(`video-source-camera-primary`) as HTMLElement,
channelId: state.channelId,
sourceType: VideoSourceType.VideoSourceCameraPrimary,
})
}, 1000);
}
} else {
if (String(res.data).length === 9) {
// 摄像头
setCurrentLookUserStatus(3)
setCurrentVideoId('3')
setTimeout(() => {
agora.setupRemoteVideoJoin({
uid: Number(res.data),
view: document.getElementById(`video-source-remote-camera`) as HTMLElement,
channelId: state.channelId,
})
}, 1000);
} else {
// 共享屏幕
setCurrentLookUserStatus(4)
setCurrentVideoId('4')
setTimeout(() => {
agora.setupRemoteVideoJoin({
uid: Number(res.data),
view: document.getElementById(`video-source-remote-screen`) as HTMLElement,
channelId: state.channelId,
})
}, 1000);
}
}
}
})
}
// 加入房间时间
const changeCurrentSeconds = (): string => {
const duration = dayjs.duration(currentSeconds, 'seconds');
const hours = duration.hours(); // 整数小时
const minutes = duration.minutes(); // 整数分钟
const secondsRemaining = duration.seconds(); // 剩余的秒数
return `${hours > 9 ? hours : '0' + hours}:${minutes > 9 ? minutes : '0' + minutes}:${secondsRemaining > 9 ? secondsRemaining : '0' + secondsRemaining}`
}
// 操作按钮
const changeStatusList = async (row: any, itemIndex: number, rowIndex: number): Promise<void> => {
const footerListTemplate = [...footerList]
setFooterListIndex({
itemIndex,
rowIndex,
})
switch (row.title) {
case '成员列表':
setStatusList({
userList: statusList.userList ? false : true,
userChatList: false,
})
storage.setItem('noViewChatList', 0)
setNoViewChatList(0)
break;
case '聊天':
setStatusList({
userList: false,
userChatList: statusList.userChatList ? false : true,
})
storage.setItem('noViewChatList', 0)
setNoViewChatList(0)
break;
case '共享屏幕':
getDesktopCapturerVideo()
setIsSharedScreenModal(true)
break;
case '停止共享':
agora.stopScreenCapture()
await allUserLook(user.uid)
footerListTemplate[itemIndex][rowIndex].title = '共享屏幕'
break;
case '关闭声音':
footerListTemplate[itemIndex][rowIndex].title = '开启声音'
footerListTemplate[itemIndex][rowIndex].active = true
setFooterList(footerListTemplate)
postOpenMicr(false, user.uid)
break;
case '开启声音':
footerListTemplate[itemIndex][rowIndex].title = '关闭声音'
footerListTemplate[itemIndex][rowIndex].active = false
setFooterList(footerListTemplate)
postOpenMicr(true, user.uid)
break;
case '关闭视频':
footerListTemplate[itemIndex][rowIndex].title = '开启视频'
footerListTemplate[itemIndex][rowIndex].active = true
setFooterList(footerListTemplate)
await postOpenCamera(false, user.uid)
await agora.stopCameraCapture();
break;
case '开启视频':
footerListTemplate[itemIndex][rowIndex].title = '关闭视频'
footerListTemplate[itemIndex][rowIndex].active = false
setFooterList(footerListTemplate)
await postOpenCamera(true, user.uid)
await agora.startCameraCapture()
break;
case '设置向导':
// stupWizardRef.current.changeModal()
break;
case '邀请人员':
invitingPersonnelRef.current.changeInvitingPersonnelModal()
break;
case '录制':
window.electron.getSources().then((sources: any) => {
const screenId = sources[0].id;
navigator.mediaDevices.getUserMedia({
audio: {
mandatory: {
chromeMediaSource: 'desktop',
chromeMediaSourceId: screenId,
}
} as any,
video: {
mandatory: {
chromeMediaSource: 'desktop',
chromeMediaSourceId: screenId,
}
} as any
}).then(async (steam) => {
const audioTracks = await navigator.mediaDevices
.getUserMedia({ audio: true, video: false })
.then(audioStream => audioStream.getAudioTracks()[0]);
steam.addTrack(audioTracks);
setMediaStream(steam)
setRecorder(new MediaRecorder(steam))
})
})
const setting = await JSON.parse(storage.getItem('setting') as string)
try {
await fs.access(setting.recordingFilesPath, fs.constants.F_OK)
footerListTemplate[itemIndex][rowIndex].title = '录制中'
footerListTemplate[itemIndex][rowIndex].active = true
setFooterList(footerListTemplate)
} catch (error: any) {
if (error.code === 'ENOENT') {
message.error('文件夹不存在!')
return
} else {
message.error(error)
}
}
break;
case '录制中':
footerListTemplate[itemIndex][rowIndex].title = '录制'
footerListTemplate[itemIndex][rowIndex].active = false
setFooterList(footerListTemplate)
stopRecorderMedia()
break;
case '共享文件':
sharedFilesModelRef.current.getData()
break;
}
}
// 停止录制
const stopRecorderMedia = async (): Promise<void> => {
if (recorder) {
await recorder.stop();
}
if (mediaStream) {
await mediaStream.getTracks().forEach((track: any) => {
track.stop()
});
}
setRecorder('')
setMediaStream('')
}
// 退出房间
const leaveChannel = async (bool?: boolean): Promise<void> => {
await stopRecorderMedia()
if (!bool) {
await onInvoke('levelChannel', {
roomNum: state.channelId
})
}
agora.leaveChannel()
navigate('/home/index')
}
// 分享屏幕
const clickSharedScreen = async (): Promise<void> => {
let data = sharedScreenList.find((item: any) => item.sourceId === sharedScreenItem.sourceId)
if (data) {
const footerListTemplate = [...footerList]
footerListTemplate[footerListIndex.itemIndex][footerListIndex.rowIndex].title = '停止共享'
setIsSharedScreenModal(false)
await agora.setDesktopCapturerVideo(sharedScreenItem, isComputerAudio, isFluencyPriority)
await allUserLook(user.screenShareId)
} else {
message.error('请选择应用!')
}
}
// 获取桌面可共享屏幕的引用
const getDesktopCapturerVideo = (): void => {
agora.getDesktopCapturerVideo().then((res: any) => {
if (sharedScreenList.length !== res.length) {
res.forEach((item: any) => {
if (item.thumbImage.buffer) {
item.thumbnailUrl = thumbImageBufferToBase64(item.thumbImage)
}
if (item.iconImage.buffer) {
item.iconDataUrl = thumbImageBufferToBase64(item.iconImage)
}
})
setSharedScreenList(res)
}
})
};
// 设置全员看谁
const allUserLook = async (uid: string): Promise<void> => {
await PostShowUser(state.channelId, uid).then(res => {
if (res.code === 200) {
getShowUser()
}
})
}
// 获取房间用户
const getRoomUser = async (): Promise<void> => {
Promise.all([
GetRoomUser(state.channelId),
GetUserList({
pageIndex: 1,
pageSize: 9999,
searchKeywod: '',
isOnline: true,
})
]).then(res => {
if (res[0].code === 200 && res[1].code === 200) {
res[0].data.forEach((item: any) => {
item.isShow = true;
item.uid = item.id;
})
setRoomUserList(res[0].data)
res[1].data.items.forEach((item: any) => {
item.uid = item.id;
const itemUser = res[0].data.find((row: any) => row.id === item.id)
if (itemUser) {
item.isRoom = true;
for (const itemUserKey in itemUser) {
for (const key in item) {
if (itemUserKey === key) {
item[key] = itemUser[itemUserKey];
}
}
}
} else {
item.isRoom = false;
}
});
allUserListArr = res[0].data
let userItem = allUserListArr.find((item: any) => item.uid === currentLookUserAccountId || item.screenShareId === currentLookUserAccountId)
if (userItem) {
setCurrentLookUserAccount(userItem)
}
setAllUserList(res[1].data.items);
}
})
}
const handleCustomStorageChange = async (e: any): Promise<void> => {
switch (e.key) {
case 'meetingMode':
setMeetingMode(e.value)
break;
case 'reconnect':
if (e.value == true) {
storage.setItem('reconnect', false)
await onInvoke('joinChannel', {
roomNum: state.channelId,
enableMicr: true,
enableCamera: true
})
await getRoomUser()
}
break;
}
};
// 聊天发送
const sendMsg = (): void => {
if (textMsg) {
onInvoke('sendChannelMsg', {
roomNum: state.channelId,
msg: textMsg,
})
setChatList((newChatList: any) => [...newChatList, {
uid: state.uid,
userName: user.userName,
message: textMsg,
}])
setTextMsg('')
} else {
message.error('请输入内容!')
}
}
// 开关麦克风
const postOpenMicr = async (enableMicr: boolean, uid: string, isAll?: boolean): Promise<void> => {
await PostOpenMicr({
roomNum: state.channelId,
uid,
enableMicr,
isAll,
})
}
// 开关视频
const postOpenCamera = async (enableCamera: boolean, uid: string): Promise<void> => {
await PostOpenCamera({
roomNum: state.channelId,
uid,
enableCamera
})
}
// 演讲者模式
const changeSpeakerMode = (): void => {
speakerModeModalRef.current.changeSpeakerMode()
}
// 获取当前模式样式
const getMeetingContentBodyLeftModeClass = (): string => {
switch (meetingMode) {
case 'FreedomMode':
return styles.meetingContentBodyLeftFreedomMode
case 'StandardMode':
return styles.meetingContentBodyLeftStandardMode
case 'SpeakerMode':
return styles.meetingContentBodyLeftSpeakerMode
case 'SingleScreenMode':
return styles.meetingContentBodyLeftSingleScreenMode
case 'DualScreenMode':
return styles.meetingContentBodyLeftDualScreenMode
case 'FourScreenMode':
return styles.meetingContentBodyLeftFourScreenMode
}
return ''
}
// 获取当前模式文字
const getMeetingContentBodyLeftModeText = (): string => {
switch (meetingMode) {
case 'FreedomMode':
return '自由者模式'
case 'StandardMode':
return '标准模式'
case 'SpeakerMode':
return '演讲者模式'
case 'SingleScreenMode':
return '单画面模式'
case 'DualScreenMode':
return '二分屏模式'
case 'FourScreenMode':
return '四分屏模式'
}
return ''
}
// 设置单个视频样式
const setMeetingContentSwiperCardClass = (uid: string): string => {
if ((currentVideoId === uid) && (meetingMode === 'StandardMode' || meetingMode === 'SpeakerMode')) {
switch (meetingMode) {
case 'StandardMode':
return styles.meetingContentSwiperCardStandardMode
case 'SpeakerMode':
return styles.meetingContentSwiperCardSpeakerMode
}
}
return ''
}
return (
<>
<div className={styles.meeting}>
<div className={styles.meetingHeader}>
<div>
<div>
{networkIcon(currentEffective)}
</div>
<div>{changeCurrentSeconds()}</div>
</div>
<div>{state.channelId}</div>
<div className='drag'>
<Popover
content={
<div className='modePopover'>
<div onClick={() => {
setModeOpen(false)
storage.setItem('meetingMode', 'StandardMode')
}}></div>
<div onClick={() => {
setModeOpen(false)
storage.setItem('meetingMode', 'SpeakerMode')
}}></div>
</div>
}
title=""
trigger="click"
open={modeOpen}
onOpenChange={() => setModeOpen(true)}
>
<div className={styles.meetingGrayButton}>{getMeetingContentBodyLeftModeText()}</div>
</Popover>
{/* <div className={styles.meetingGrayButton} onClick={changeSpeakerMode}>{getMeetingContentBodyLeftModeText()}</div> */}
<Operation></Operation>
</div>
</div>
<div className={styles.meetingContent}>
<div className={styles.meetingContentBody}>
<div className={`${styles.meetingContentBodyLeft} drag`}>
<div className={getMeetingContentBodyLeftModeClass()} >
{allUserList.map((item: any, index: number) => {
return (
item.isRoom ?
<div
className={`${styles.meetingContentSwiperCard} ${setMeetingContentSwiperCardClass(item.uid)}`}
key={index}
onClick={() => {
setCurrentVideoId(item.uid)
}}
>
<div className={`${styles.meetingContentSwiperCardVdeio} ${currentVideoId === item.uid ? styles.active : ''}`} id={`video-${item.uid}`}>
<div className={styles.meetingContentSwiperCardVdeioLoading}>
<Avatar name={item.userName} />
</div>
</div>
{meetingContentUser(item)}
{item.enableCamera ? null : meetingContentError(currentVideoId, item)}
</div> : null
)
}
)}
{currentLookUserStatus === 1 && currentLookUserAccount ?
<div className={`${styles.meetingContentSwiperCard} ${setMeetingContentSwiperCardClass('1')}`} onClick={() => setCurrentVideoId('1')}>
<div className={`${styles.meetingContentSwiperCardVdeio}`} id='video-source-camera-primary'>
{<div className={styles.meetingContentSwiperCardVdeioLoading}>
<Avatar name={currentLookUserAccount.userName} />
</div>}
</div>
{meetingContentUser(currentLookUserAccount)}
{currentLookUserAccount.enableCamera ? null : meetingContentError(currentVideoId, currentLookUserAccount)}
</div> : null}
{currentLookUserStatus === 2 && currentLookUserAccount ?
<div className={`${styles.meetingContentSwiperCard} ${setMeetingContentSwiperCardClass('2')}`} onClick={() => setCurrentVideoId('2')}>
<div className={`${styles.meetingContentSwiperCardVdeio}`} id='video-source-screen'>
<div className={styles.meetingContentSwiperCardVdeioLoading}>
<Avatar name={currentLookUserAccount.userName} />
</div>
</div>
{meetingContentUser(currentLookUserAccount)}
{currentLookUserAccount.enableCamera ? null : meetingContentError(currentVideoId, currentLookUserAccount)}
</div> : null}
{currentLookUserStatus === 3 && currentLookUserAccount ?
<div className={`${styles.meetingContentSwiperCard} ${setMeetingContentSwiperCardClass('3')}`} onClick={() => setCurrentVideoId('3')}>
<div className={`${styles.meetingContentSwiperCardVdeio}`} id='video-source-remote-camera'>
<div className={styles.meetingContentSwiperCardVdeioLoading}>
<Avatar name={currentLookUserAccount.userName} />
</div>
</div>
{meetingContentUser(currentLookUserAccount)}
{currentLookUserAccount.enableCamera ? null : meetingContentError(currentVideoId, currentLookUserAccount)}
</div> : null}
{currentLookUserStatus === 4 && currentLookUserAccount ?
<div className={`${styles.meetingContentSwiperCard} ${setMeetingContentSwiperCardClass('4')}`} onClick={() => setCurrentVideoId('4')}>
<div className={`${styles.meetingContentSwiperCardVdeio}`} id='video-source-remote-screen'>
<div className={styles.meetingContentSwiperCardVdeioLoading}>
<Avatar name={currentLookUserAccount.userName} />
</div>
</div>
{meetingContentUser(currentLookUserAccount)}
{currentLookUserAccount.enableCamera ? null : meetingContentError(currentVideoId, currentLookUserAccount)}
</div> : null}
</div>
</div>
{
(statusList.userList || statusList.userChatList) ? (
<div className={styles.meetingContentBodyRight}>
{statusList.userList ?
<div className={styles.meetingUserList}>
<div className={styles.meetingUserListTitle}>
<span></span>
<img src={ImageUrl.icon18} alt="" className='drag' onClick={() => {
setStatusList({
userList: false,
userChatList: false,
})
}} />
</div>
<div style={{ padding: '0 14px', boxSizing: 'border-box' }}>
<Input
placeholder="请输入用户名"
className='drag'
prefix={<SearchOutlined style={{ color: 'white' }} />}
value={userSearchValue}
onChange={(e) => {
setUserSearchValue(e.target.value)
const newRoomUserList = [...roomUserList]
newRoomUserList.forEach(row => {
if (e.target.value) {
if (row.userName.indexOf(e.target.value) !== -1) {
row.isShow = true;
} else {
row.isShow = false;
}
} else {
row.isShow = true;
}
});
setRoomUserList(newRoomUserList)
}}
/>
</div>
<div className={styles.meetingUserListContent}>
{roomUserList.map((item: any, index: number) => {
return (
item.isShow ? <div key={index + item.id} className='drag'>
<div>
<div><Avatar name={item.userName} /></div>
<span>
{item.userName}
{item.roleId === '1' || item.isManager ?
<span style={{ color: '#02B188', marginLeft: '4px' }}>
{item.roleId === '1' ? '主持人' : '临时主持人'}
</span>
: null}
</span>
</div>
<div>
<img src={item.enableMicr ? ImageUrl.icon22 : ImageUrl.icon22Active} alt="" onClick={() => {
postOpenMicr(!item.enableMicr, item.id)
}} title={item.enableMicr ? '关闭声音' : '打开声音'} />
<img src={item.enableCamera ? ImageUrl.icon23 : ImageUrl.icon23Active} alt="" onClick={() => {
postOpenCamera(!item.enableCamera, item.id)
}} title={item.enableCamera ? '关闭视频' : '打开视频'} />
</div>
{item.uid !== user.uid && user.roleId === '1' ? <div className='drag'>
{!item.isManager ? <Button
type="primary"
className='m-ant-btn'
style={{ marginBottom: '10px', width: '80%' }}
size={'small'}
onClick={() => {
PostRoomManager(state.roomId, [item.id]).then(res => {
if (res.code === 200) {
onInvoke('sendOper', {
roomNum: state.channelId,
type: 2,
})
}
})
}}
></Button> : <Button
type="primary"
className='m-ant-btn'
style={{ marginBottom: '10px', width: '80%' }}
size={'small'}
onClick={() => {
DeleteRoomManager(state.roomId, [item.id]).then(res => {
if (res.code === 200) {
onInvoke('sendOper', {
roomNum: state.channelId,
type: 2,
})
}
})
}}
></Button>}
<Button
type="primary"
className='m-ant-btn'
style={{ width: '80%' }}
size={'small'}
onClick={() => {
GetRoomKickout(state.channelId, item.id).then(res => {
if (res.code === 200) {
onInvoke('sendOper', {
roomNum: state.channelId,
type: 3,
})
}
})
}}
></Button>
</div> : null}
</div> : null
)
}
)}
</div>
<div className={`${styles.meetingUserListFooter} drag`}>
<div onClick={() => invitingPersonnelRef.current.changeInvitingPersonnelModal()}></div>
<div onClick={() => postOpenMicr(false, user.id, true)}></div>
</div>
</div>
:
<div className={styles.meetingUserChat} id='meetingUserChat'>
<div className={styles.meetingUserChatTitle}>
<span></span>
<img src={ImageUrl.icon18} alt="" className='drag' onClick={() => {
setStatusList({
userList: false,
userChatList: false,
})
}} />
</div>
<div className={styles.meetingUserChatContent}>
{chatList.map((item: any, index: number) =>
<div
key={index}
className={`${item.uid !== state.uid ? styles.meetingUserChatContentLeft : styles.meetingUserChatContentRight} drag`}>
<div>
<div><Avatar name={item.userName} /></div>
<span>{item.userName}</span>
</div>
<div>{item.message}</div>
</div>
)}
</div>
<div className={`${styles.meetingUserChatInput} drag`}>
<Input.TextArea placeholder="请输入消息" value={textMsg} style={{ flexGrow: 1 }} onChange={(e) => {
setTextMsg(e.target.value)
}}></Input.TextArea>
<Button type="primary" className='m-ant-btn' style={{ flexShrink: 0, marginTop: '4px' }} onClick={sendMsg}></Button>
</div>
</div>
}
</div>
) : null
}
</div>
<div className={styles.meetingContentFooter}>
{footerList.map((item, itemIndex) => {
return (
<div key={itemIndex}>
{item.map((row, rowIndex) => {
return (
row.title === '结束' ?
<Popover key={rowIndex}
content={
<div className='meetingContentFooterPopover'>
<div onClick={async () => {
await onInvoke('sendOper', {
roomNum: state.channelId,
type: 1,
})
leaveChannel(true)
}}></div>
<div onClick={() => leaveChannel()}></div>
<div onClick={() => { setOpen(false) }}></div>
</div>
}
title=""
trigger="click"
open={open}
onOpenChange={() => setOpen(true)}
>
<div className='drag'>
<img src={row.active ? row.iconActive : row.icon} alt="" />
<span>{row.title}</span>
</div>
</Popover> :
<div className='drag' onClick={() => changeStatusList(row, itemIndex, rowIndex)} key={rowIndex}>
<img src={row.active ? row.iconActive : row.icon} alt="" />
<span>{row.title}</span>
{row.title === '成员列表' ? <div>{roomUserList.length}</div> : null}
{row.title === '聊天' && noViewChatList > 0 ? <div>{noViewChatList}</div> : null}
</div>
)
})}
</div>
)
})}
</div>
</div>
</div>
<Modal title="共享屏幕" open={isSharedScreenModal} footer={null} closable={false} centered width={'800px'}>
<div className={styles.sharedScreenModal}>
<div>
{sharedScreenList.map((item: any, index: number) => {
return (
<div
className={sharedScreenItem.sourceId === item.sourceId ? styles.active : ''}
key={index}
onClick={() => {
setSharedScreenItem(item)
}}>
{item.iconDataUrl ? <img src={item.iconDataUrl} alt="" /> : ''}
<div><img src={item.thumbnailUrl} alt="" /></div>
<span>{item.sourceTitle}</span>
</div>
)
})}
</div>
<div>
<div>
<Checkbox onChange={(e) => {
setIsComputerAudio(e.target.checked)
}} checked={isComputerAudio}></Checkbox>
<Checkbox onChange={(e) => {
setIsFluencyPriority(e.target.checked)
}} checked={isFluencyPriority}></Checkbox>
</div>
<div>
<Button type="primary" onClick={() => { setIsSharedScreenModal(false) }} style={{ backgroundColor: '#31353A', marginRight: '14px' }}></Button>
<Button type="primary" className='m-ant-btn' onClick={() => clickSharedScreen()}></Button>
</div>
</div>
</div>
</Modal>
<SharedFilesModel ref={sharedFilesModelRef} />
<SpeakerModeModal ref={speakerModeModalRef} />
<InvitingPersonnelModal ref={invitingPersonnelRef} />
<StupWizard ref={stupWizardRef} />
</>
)
}
const meetingContentUser = (item: any) => {
return (
<>
<div className={styles.meetingContentUser}>
{item.roleId === '1' || item.isManager ? <div className={styles.meetingContentUserRole}>
<img src={ImageUrl.icon32} alt="" />
</div> : null}
<div className={styles.meetingContentUserName}>
<img src={item.enableMicr ? ImageUrl.icon22 : ImageUrl.icon22Active} alt="" />
<span>{item.userName}</span>
</div>
</div>
</>
)
}
const meetingContentError = (currentVideoId: any, item: any) => {
return (
<>
<div className={`${styles.meetingContentError} ${currentVideoId === item.uid ? styles.active : ''}`}>
<Avatar name={item.userName} />
</div>
</>
)
}
const networkIcon = (network: number) => {
switch (network) {
case 0:
return <svg width="32" height="33" viewBox="0 0 32 33" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clipPath="url(#clip0_5499_5160)">
<path d="M1.9 32.8398C0.85 32.8398 0 31.9898 0 30.9398V23.3398C0 22.2898 0.85 21.4398 1.9 21.4398C2.95 21.4398 3.8 22.2898 3.8 23.3398V30.9398C3.8 31.9898 2.95 32.8398 1.9 32.8398ZM11.3 32.8398C10.25 32.8398 9.4 31.9898 9.4 30.9398V16.1398C9.4 15.0898 10.25 14.2398 11.3 14.2398C12.35 14.2398 13.2 15.0898 13.2 16.1398V30.9398C13.2 31.9898 12.35 32.8398 11.3 32.8398ZM20.7 32.8398C19.65 32.8398 18.8 31.9898 18.8 30.9398V9.73984C18.8 8.68984 19.65 7.83984 20.7 7.83984C21.75 7.83984 22.6 8.68984 22.6 9.73984V30.9398C22.6 31.9898 21.75 32.8398 20.7 32.8398ZM30.1 32.8398C29.05 32.8398 28.2 31.9898 28.2 30.9398V2.73984C28.2 1.68984 29.05 0.839844 30.1 0.839844C31.15 0.839844 32 1.68984 32 2.73984V30.9398C32 31.9898 31.15 32.8398 30.1 32.8398Z" fill="#7C8280" />
<path d="M8.86265 3.02995L6.10877 5.78383L3.35489 3.02995C2.99725 2.67231 2.40713 2.67231 2.04948 3.02995C1.69184 3.3876 1.69184 3.97772 2.04948 4.33536L4.80336 7.08924L2.04948 9.84312C1.69184 10.2008 1.69184 10.7909 2.04948 11.1485C2.40713 11.5062 2.99725 11.5062 3.35489 11.1485L6.10877 8.39465L8.86265 11.1485C9.22029 11.5062 9.81041 11.5062 10.1681 11.1485C10.5257 10.7909 10.5257 10.2008 10.1681 9.84312L7.41418 7.08924L10.1681 4.33536C10.5257 3.97772 10.5257 3.3876 10.1681 3.02995C9.81041 2.67231 9.22029 2.67231 8.86265 3.02995Z" fill="#A5A5A5" />
</g>
<defs>
<clipPath id="clip0_5499_5160">
<rect width="32" height="32" fill="white" transform="translate(0 0.839844)" />
</clipPath>
</defs>
</svg>
case 1:
return <svg width="32" height="33" viewBox="0 0 32 33" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.9 32.8385C0.85 32.8385 0 31.9885 0 30.9385V23.3385C0 22.2885 0.85 21.4385 1.9 21.4385C2.95 21.4385 3.8 22.2885 3.8 23.3385V30.9385C3.8 31.9885 2.95 32.8385 1.9 32.8385Z" fill="#02B188" />
<path d="M28.1992 30.9389C28.1992 31.9889 29.0492 32.8389 30.0992 32.8389C31.1492 32.8389 31.9992 31.9889 31.9992 30.9389V2.73887C31.9992 1.68887 31.1492 0.838867 30.0992 0.838867C29.0492 0.838867 28.1992 1.68887 28.1992 2.73887V30.9389Z" fill="#7C8280" />
<path d="M18.8008 30.9389C18.8008 31.9889 19.6508 32.8389 20.7008 32.8389C21.7508 32.8389 22.6008 31.9889 22.6008 30.9389V9.73887C22.6008 8.68887 21.7508 7.83887 20.7008 7.83887C19.6508 7.83887 18.8008 8.68887 18.8008 9.73887V30.9389Z" fill="#7C8280" />
<path d="M9.40039 30.9393C9.40039 31.9893 10.2504 32.8393 11.3004 32.8393C12.3504 32.8393 13.2004 31.9893 13.2004 30.9393V16.1393C13.2004 15.0893 12.3504 14.2393 11.3004 14.2393C10.2504 14.2393 9.40039 15.0893 9.40039 16.1393V30.9393Z" fill="#7C8280" />
</svg>
case 2:
return <svg width="32" height="33" viewBox="0 0 32 33" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.9 32.8385C0.85 32.8385 0 31.9885 0 30.9385V23.3385C0 22.2885 0.85 21.4385 1.9 21.4385C2.95 21.4385 3.8 22.2885 3.8 23.3385V30.9385C3.8 31.9885 2.95 32.8385 1.9 32.8385Z" fill="#02B188" />
<path d="M28.1992 30.9389C28.1992 31.9889 29.0492 32.8389 30.0992 32.8389C31.1492 32.8389 31.9992 31.9889 31.9992 30.9389V2.73887C31.9992 1.68887 31.1492 0.838867 30.0992 0.838867C29.0492 0.838867 28.1992 1.68887 28.1992 2.73887V30.9389Z" fill="#7C8280" />
<path d="M18.8008 30.9389C18.8008 31.9889 19.6508 32.8389 20.7008 32.8389C21.7508 32.8389 22.6008 31.9889 22.6008 30.9389V9.73887C22.6008 8.68887 21.7508 7.83887 20.7008 7.83887C19.6508 7.83887 18.8008 8.68887 18.8008 9.73887V30.9389Z" fill="#7C8280" />
<path d="M9.40039 30.9393C9.40039 31.9893 10.2504 32.8393 11.3004 32.8393C12.3504 32.8393 13.2004 31.9893 13.2004 30.9393V16.1393C13.2004 15.0893 12.3504 14.2393 11.3004 14.2393C10.2504 14.2393 9.40039 15.0893 9.40039 16.1393V30.9393Z" fill="#02B188" />
</svg>
case 3:
return <svg width="32" height="33" viewBox="0 0 32 33" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.9 32.8385C0.85 32.8385 0 31.9885 0 30.9385V23.3385C0 22.2885 0.85 21.4385 1.9 21.4385C2.95 21.4385 3.8 22.2885 3.8 23.3385V30.9385C3.8 31.9885 2.95 32.8385 1.9 32.8385Z" fill="#02B188" />
<path d="M28.1992 30.9389C28.1992 31.9889 29.0492 32.8389 30.0992 32.8389C31.1492 32.8389 31.9992 31.9889 31.9992 30.9389V2.73887C31.9992 1.68887 31.1492 0.838867 30.0992 0.838867C29.0492 0.838867 28.1992 1.68887 28.1992 2.73887V30.9389Z" fill="#7C8280" />
<path d="M18.8008 30.9389C18.8008 31.9889 19.6508 32.8389 20.7008 32.8389C21.7508 32.8389 22.6008 31.9889 22.6008 30.9389V9.73887C22.6008 8.68887 21.7508 7.83887 20.7008 7.83887C19.6508 7.83887 18.8008 8.68887 18.8008 9.73887V30.9389Z" fill="#02B188" />
<path d="M9.40039 30.9393C9.40039 31.9893 10.2504 32.8393 11.3004 32.8393C12.3504 32.8393 13.2004 31.9893 13.2004 30.9393V16.1393C13.2004 15.0893 12.3504 14.2393 11.3004 14.2393C10.2504 14.2393 9.40039 15.0893 9.40039 16.1393V30.9393Z" fill="#02B188" />
</svg>
case 4:
return <svg width="32" height="33" viewBox="0 0 32 33" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.9 32.8385C0.85 32.8385 0 31.9885 0 30.9385V23.3385C0 22.2885 0.85 21.4385 1.9 21.4385C2.95 21.4385 3.8 22.2885 3.8 23.3385V30.9385C3.8 31.9885 2.95 32.8385 1.9 32.8385Z" fill="#02B188" />
<path d="M28.1992 30.9389C28.1992 31.9889 29.0492 32.8389 30.0992 32.8389C31.1492 32.8389 31.9992 31.9889 31.9992 30.9389V2.73887C31.9992 1.68887 31.1492 0.838867 30.0992 0.838867C29.0492 0.838867 28.1992 1.68887 28.1992 2.73887V30.9389Z" fill="#02B188" />
<path d="M18.8008 30.9389C18.8008 31.9889 19.6508 32.8389 20.7008 32.8389C21.7508 32.8389 22.6008 31.9889 22.6008 30.9389V9.73887C22.6008 8.68887 21.7508 7.83887 20.7008 7.83887C19.6508 7.83887 18.8008 8.68887 18.8008 9.73887V30.9389Z" fill="#02B188" />
<path d="M9.40039 30.9393C9.40039 31.9893 10.2504 32.8393 11.3004 32.8393C12.3504 32.8393 13.2004 31.9893 13.2004 30.9393V16.1393C13.2004 15.0893 12.3504 14.2393 11.3004 14.2393C10.2504 14.2393 9.40039 15.0893 9.40039 16.1393V30.9393Z" fill="#02B188" />
</svg>
}
}
export default Meeting