962 lines
37 KiB
TypeScript
962 lines
37 KiB
TypeScript
import styles from '@/page/Meeting/index.module.scss'
|
||
import { useEffect, useRef, useState } from "react";
|
||
import Operation from '@/components/Operation';
|
||
import { Button, Input, Popover, Modal, Checkbox, message, Table, Pagination } from "antd";
|
||
import { DeleteOutlined, ProfileOutlined, ReloadOutlined, SearchOutlined, VerticalAlignBottomOutlined } from '@ant-design/icons';
|
||
import { useLocation, useNavigate } from 'react-router-dom';
|
||
import { thumbImageBufferToBase64 } from '@/utils/package/base64'
|
||
import { storage } from '@/utils';
|
||
import { GetRoomFile, PostRoomFile, DeleteRoomFile, GetRoomUpFileurl, GetRoomFileDwUrl, GetRoomUser, PostOpenMicr, PostOpenCamera, PostRoomManager, DeleteRoomManager, GetRoomKickout } from '@/api/Meeting';
|
||
import axios from 'axios';
|
||
import ImageUrl from '@/utils/package/imageUrl'
|
||
import agora from '@/utils/package/agora'
|
||
import SpeakerModeModal from '@/components/SpeakerModeModal';
|
||
import { onInvoke, onSignalr, offSignalr } from '@/utils/package/signalr';
|
||
import dayjs from 'dayjs';
|
||
import durationPlugin from 'dayjs/plugin/duration';
|
||
import { VideoSourceType } from 'agora-electron-sdk';
|
||
import InvitingPersonnelModal from '@/components/InvitingPersonnelModal';
|
||
dayjs.extend(durationPlugin);
|
||
const { Column } = Table
|
||
const Meeting: React.FC = () => {
|
||
const navigate = useNavigate();
|
||
const { state } = useLocation();
|
||
const speakerModeModalRef = useRef<any>();
|
||
const invitingPersonnelRef = useRef<any>();
|
||
const [statusList, setStatusList] = useState({
|
||
userList: false,
|
||
userChatList: false,
|
||
})
|
||
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
||
const [isSharedScreenModal, setIsSharedScreenModal] = useState(false);
|
||
const [isInit, setIsInit] = useState(true);
|
||
const [user, setUser] = useState<any>({});
|
||
const [showRowSelection, setShowRowSelection] = useState(false);
|
||
const [isSharedFilesModel, setIsSharedFilesModel] = useState(false);
|
||
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 [fileList, setFileList] = useState({
|
||
data: [],
|
||
keyword: '',
|
||
total: 0,
|
||
pageIndex: 1,
|
||
pageSize: 10,
|
||
})
|
||
const [roomUserList, setRoomUserList] = useState<any>([])
|
||
const [chatList, setChatList] = useState<any>([])
|
||
const [currentVideoId, setCurrentVideoId] = useState('')
|
||
let [currentSeconds, setCurrentSeconds] = useState(0)
|
||
const [currentEffective, setCurrentEffective] = useState(0)
|
||
const [open, setOpen] = useState(false)
|
||
const [meetingMode, setMeetingMode] = useState('')
|
||
const [userSearchValue, setUserSearchValue] = useState('')
|
||
useEffect(() => {
|
||
let time = null as any;
|
||
if (isInit) {
|
||
let userInfo = JSON.parse(storage.getItem('user') as string)
|
||
setMeetingMode(storage.getItem('meetingMode') as string || 'FreedomMode');
|
||
agora.init()
|
||
agora.registerEventHandler({
|
||
onJoinChannelSuccess: async (info: any, _elapsed: any) => {
|
||
await onInvoke('joinChannel', {
|
||
roomNum: info.channelId,
|
||
enableMicr: true,
|
||
enableCamera: true
|
||
})
|
||
await getRoomUser()
|
||
setTimeout(() => {
|
||
agora.setupLocalVideo({
|
||
account: Number(info.localUid),
|
||
view: document.getElementById(`video-${info.localUid}`) as HTMLElement,
|
||
channelId: info.channelId,
|
||
})
|
||
}, 1000);
|
||
},
|
||
onUserJoined: async (info: any, remoteUid: any, _elapsed: any) => {
|
||
await getRoomUser()
|
||
setTimeout(() => {
|
||
agora.setupRemoteVideoJoin({
|
||
account: Number(remoteUid),
|
||
view: document.getElementById(`video-${remoteUid}`) as HTMLElement,
|
||
channelId: info.channelId,
|
||
})
|
||
}, 1000);
|
||
},
|
||
onUserOffline: async (info: any, remoteUid: any, reason: any) => {
|
||
await onInvoke('levelChannel', {
|
||
roomNum: info.channelId
|
||
})
|
||
agora.setupRemoteVideo({
|
||
account: Number(remoteUid),
|
||
view: document.getElementById(`video-${remoteUid}`) as HTMLElement,
|
||
channelId: info.channelId,
|
||
})
|
||
}
|
||
})
|
||
agora.setCameraCapture(VideoSourceType.VideoSourceCameraPrimary)
|
||
agora.setJoinChannel({
|
||
channelId: state.channelId,
|
||
userid: userInfo.account,
|
||
token: state.token,
|
||
})
|
||
setCurrentVideoId(userInfo.account)
|
||
setUser(userInfo)
|
||
setIsInit(false)
|
||
window.addEventListener('customStorageChange', handleCustomStorageChange);
|
||
time = setInterval(() => {
|
||
let effectiveTypeLength = ['slow-2g', '2g', '3g', '4g'].indexOf((navigator as any).connection.effectiveType)
|
||
if (effectiveTypeLength >= 0) {
|
||
setCurrentEffective(effectiveTypeLength + 1)
|
||
}
|
||
setCurrentSeconds(currentSeconds++)
|
||
}, 1000)
|
||
} else {
|
||
getRoomFile()
|
||
}
|
||
return () => {
|
||
window.removeEventListener('customStorageChange', handleCustomStorageChange);
|
||
clearInterval(time)
|
||
};
|
||
}, [fileList.pageIndex]);
|
||
|
||
useEffect(() => {
|
||
roomUserList.forEach((item: any) => {
|
||
if (item.account === user.account) {
|
||
const footerListTemplate = [...footerList]
|
||
footerListTemplate[0][0].title = item.enableMicr ? '关闭声音' : '开启声音'
|
||
footerListTemplate[0][0].active = !item.enableMicr
|
||
setFooterList(footerListTemplate)
|
||
}
|
||
agora.muteLocalAudioStream(!item.enableMicr)
|
||
agora.muteLocalVideoStream(!item.enableCamera)
|
||
});
|
||
}, [roomUserList]);
|
||
|
||
useEffect(() => {
|
||
onSignalr((item: any) => {
|
||
switch (item.key) {
|
||
case 'ReceiveMessage':
|
||
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;
|
||
}
|
||
})
|
||
return () => {
|
||
offSignalr()
|
||
}
|
||
}, [])
|
||
|
||
useEffect(() => {
|
||
|
||
}, [currentVideoId])
|
||
|
||
// 加入房间时间
|
||
const changeCurrentSeconds = () => {
|
||
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: true,
|
||
userChatList: false,
|
||
})
|
||
break;
|
||
case '聊天':
|
||
setStatusList({
|
||
userList: false,
|
||
userChatList: true,
|
||
})
|
||
break;
|
||
case '共享屏幕':
|
||
getDesktopCapturerVideo()
|
||
setIsSharedScreenModal(true)
|
||
break;
|
||
case '停止共享':
|
||
agora.setCameraCapture(VideoSourceType.VideoSourceCameraPrimary)
|
||
footerListTemplate[itemIndex][rowIndex].title = '共享屏幕'
|
||
break;
|
||
case '关闭声音':
|
||
footerListTemplate[itemIndex][rowIndex].title = '开启声音'
|
||
footerListTemplate[itemIndex][rowIndex].active = true
|
||
setFooterList(footerListTemplate)
|
||
postOpenMicr(false)
|
||
break;
|
||
case '开启声音':
|
||
footerListTemplate[itemIndex][rowIndex].title = '关闭声音'
|
||
footerListTemplate[itemIndex][rowIndex].active = false
|
||
setFooterList(footerListTemplate)
|
||
postOpenMicr(true)
|
||
break;
|
||
case '关闭视频':
|
||
footerListTemplate[itemIndex][rowIndex].title = '开启视频'
|
||
footerListTemplate[itemIndex][rowIndex].active = true
|
||
setFooterList(footerListTemplate)
|
||
postOpenCamera(false)
|
||
break;
|
||
case '开启视频':
|
||
footerListTemplate[itemIndex][rowIndex].title = '关闭视频'
|
||
footerListTemplate[itemIndex][rowIndex].active = false
|
||
setFooterList(footerListTemplate)
|
||
postOpenCamera(true)
|
||
break;
|
||
case '设置向导':
|
||
|
||
break;
|
||
case '邀请人员':
|
||
invitingPersonnelRef.current.changeInvitingPersonnelModal()
|
||
break;
|
||
case '录制':
|
||
if (currentVideoId === user.account) {
|
||
message.error('请勿自己录制自己!')
|
||
} else {
|
||
footerListTemplate[itemIndex][rowIndex].title = '录制中'
|
||
footerListTemplate[itemIndex][rowIndex].active = true
|
||
setFooterList(footerListTemplate)
|
||
agora.startRecording(Number(currentVideoId))
|
||
}
|
||
break;
|
||
case '录制中':
|
||
footerListTemplate[itemIndex][rowIndex].title = '录制'
|
||
footerListTemplate[itemIndex][rowIndex].active = false
|
||
setFooterList(footerListTemplate)
|
||
agora.stopRecording()
|
||
break;
|
||
case '共享文件':
|
||
await getRoomFile()
|
||
setIsSharedFilesModel(true)
|
||
break;
|
||
}
|
||
}
|
||
// 退出房间
|
||
const leaveChannel = (): void => {
|
||
agora.leaveChannel()
|
||
agora.stopScreenCapture()
|
||
navigate(-1)
|
||
}
|
||
// 分享屏幕
|
||
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)
|
||
agora.setDesktopCapturerVideo(sharedScreenItem, VideoSourceType.VideoSourceScreen)
|
||
} 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 getRoomFile = async (): Promise<void> => {
|
||
await GetRoomFile({
|
||
pageIndex: fileList.pageIndex,
|
||
pageSize: fileList.pageSize,
|
||
keyword: fileList.keyword,
|
||
roomId: state.roomId
|
||
}).then(res => {
|
||
if (res.code === 200) {
|
||
setFileList({
|
||
...fileList,
|
||
data: res.data.items.map((item: any) => {
|
||
return {
|
||
...item,
|
||
key: item.id,
|
||
}
|
||
}),
|
||
total: res.data.total
|
||
})
|
||
}
|
||
})
|
||
}
|
||
// 获取房间用户
|
||
const getRoomUser = async (): Promise<void> => {
|
||
await GetRoomUser(state.channelId).then(res => {
|
||
if (res.code === 200) {
|
||
setRoomUserList(res.data.map((item: any) => {
|
||
return {
|
||
isShow: true,
|
||
...item
|
||
}
|
||
}))
|
||
}
|
||
})
|
||
}
|
||
const handleCustomStorageChange = async (e: any): Promise<void> => {
|
||
switch (e.key) {
|
||
case 'meetingMode':
|
||
setMeetingMode(e.value)
|
||
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.success('请输入内容!')
|
||
}
|
||
}
|
||
// 开关麦克风
|
||
const postOpenMicr = async (enableMicr: boolean, isAll?: boolean): Promise<void> => {
|
||
await PostOpenMicr({
|
||
roomNum: state.channelId,
|
||
uid: user.uid,
|
||
enableMicr,
|
||
isAll,
|
||
})
|
||
}
|
||
// 开关视频
|
||
const postOpenCamera = async (enableCamera: boolean): Promise<void> => {
|
||
await PostOpenCamera({
|
||
roomNum: state.channelId,
|
||
uid: user.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 = (account: string): string => {
|
||
if (currentVideoId === account && (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>
|
||
{currentEffective >= 1 ? <span></span> : null}
|
||
{currentEffective >= 2 ? <span></span> : null}
|
||
{currentEffective >= 3 ? <span></span> : null}
|
||
{currentEffective >= 4 ? <span></span> : null}
|
||
</div>
|
||
<div>{changeCurrentSeconds()}</div>
|
||
</div>
|
||
<div>会议号:{state.channelId}</div>
|
||
<div className='drag'>
|
||
<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()} >
|
||
{roomUserList.map((item: any, index: number) =>
|
||
<div
|
||
className={`${styles.meetingContentSwiperCard} ${setMeetingContentSwiperCardClass(item.account)}`}
|
||
key={index}
|
||
onClick={() => {
|
||
if (footerList[1][3].active) {
|
||
return message.error('视频录制中请勿切换,或结束录制再切换!')
|
||
}
|
||
setCurrentVideoId(item.account)
|
||
}}
|
||
>
|
||
<div className={`${styles.meetingContentSwiperCardVdeio} ${currentVideoId === item.account ? styles.active : ''}`} id={`video-${item.account}`}>
|
||
<div className={styles.meetingContentSwiperCardVdeioLoading}>
|
||
暂无视频
|
||
</div>
|
||
</div>
|
||
{meetingContentUser(item)}
|
||
{item.enableCamera ? null : meetingContentError(currentVideoId, item)}
|
||
</div>
|
||
)}
|
||
</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><img src={ImageUrl.avatar} alt="" /></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="" />
|
||
<img src={item.enableCamera ? ImageUrl.icon23 : ImageUrl.icon23Active} alt="" />
|
||
</div>
|
||
{item.account !== user.account && 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`} onClick={() => invitingPersonnelRef.current.changeInvitingPersonnelModal()}>
|
||
<div>邀请</div>
|
||
<div onClick={() => postOpenMicr(false, true)}>全员静音</div>
|
||
</div>
|
||
</div>
|
||
:
|
||
<div className={styles.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><img src={ImageUrl.avatar} alt="" /></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)
|
||
}} onPressEnter={sendMsg}></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()
|
||
}}>全员结束会议</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>
|
||
</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>
|
||
<Checkbox onChange={() => {
|
||
|
||
}}>共享电脑音频</Checkbox>
|
||
<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>
|
||
<Modal
|
||
title="共享文件"
|
||
open={isSharedFilesModel}
|
||
footer={null}
|
||
centered
|
||
width={'800px'}
|
||
onCancel={() => setIsSharedFilesModel(false)}
|
||
maskClosable
|
||
>
|
||
<div>
|
||
<div className={styles.sharedFilesModel}>
|
||
<div>
|
||
<span>共{fileList.total}个文件</span>
|
||
<div style={{ color: 'white' }}>
|
||
<Input
|
||
placeholder="搜索"
|
||
style={{ width: '200px' }}
|
||
prefix={<SearchOutlined style={{ color: 'white' }} />}
|
||
onChange={(e) => {
|
||
setFileList({
|
||
...fileList,
|
||
keyword: e.target.value
|
||
})
|
||
}}
|
||
onBlur={() => {
|
||
if (fileList.pageIndex === 1) {
|
||
getRoomFile()
|
||
} else {
|
||
setFileList({
|
||
...fileList,
|
||
pageIndex: 1
|
||
})
|
||
}
|
||
}}
|
||
/>
|
||
<ReloadOutlined title='刷新' onClick={() => {
|
||
if (fileList.pageIndex === 1) {
|
||
getRoomFile()
|
||
} else {
|
||
setFileList({
|
||
...fileList,
|
||
pageIndex: 1
|
||
})
|
||
}
|
||
}} />
|
||
<ProfileOutlined title={showRowSelection ? '取消框选' : '显示框选'} onClick={() => {
|
||
setShowRowSelection(!showRowSelection)
|
||
}} style={{ color: showRowSelection ? '#5575F2' : 'white' }} />
|
||
{showRowSelection ? <DeleteOutlined title='删除' onClick={() => {
|
||
if (selectedRowKeys.length) {
|
||
DeleteRoomFile(selectedRowKeys).then(res => {
|
||
if (res.code === 200) {
|
||
message.success('删除成功!')
|
||
getRoomFile()
|
||
}
|
||
})
|
||
} else {
|
||
message.error('请选择文件!')
|
||
}
|
||
}} /> : null}
|
||
<Button type="primary" style={{ backgroundColor: '#31353A' }}
|
||
onClick={() => {
|
||
const file = document.createElement("input") as any;
|
||
file.accept = "image/*,.doc,.docx,.ppt,.pptx,.xls,.xlsx,application/pdf";
|
||
file.type = "file";
|
||
file.onchange = async () => {
|
||
const fileInfo = file.files[0];
|
||
const maxSize = 100 * 1024 * 1024; // 100MB in bytes
|
||
|
||
if (fileInfo.size > maxSize) {
|
||
message.error('文件太大!请上传小于100MB的文件。')
|
||
// 清除文件输入框的值,以便用户可以选择其他文件
|
||
return
|
||
}
|
||
const fileType = fileInfo.name.split('.');
|
||
const fileTypeName = fileType[fileType.length - 1];
|
||
await GetRoomUpFileurl(state.channelId, fileTypeName).then(async res => {
|
||
const formData = new FormData();
|
||
formData.append("name", fileInfo.name);
|
||
formData.append("OSSAccessKeyId", res.data.ossAccessKeyId);
|
||
formData.append("key", res.data.key);
|
||
formData.append("policy", res.data.policy);
|
||
formData.append("signature", res.data.signature);
|
||
formData.append("success_action_status", res.data.success_action_status);
|
||
formData.append("file", fileInfo);
|
||
await axios.post(res.data.host, formData, {
|
||
headers: {
|
||
"Content-Type": "multipart/form-data",
|
||
"Authorization": `Bearer ${user.token}`
|
||
},
|
||
withCredentials: false
|
||
})
|
||
await PostRoomFile({
|
||
fileUrl: res.data.key,
|
||
size: fileInfo.size,
|
||
fileName: fileInfo.name,
|
||
roomId: state.roomId
|
||
})
|
||
getRoomFile()
|
||
})
|
||
};
|
||
file.click();
|
||
}}
|
||
>上传</Button>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<Table
|
||
size={'small'}
|
||
rowSelection={showRowSelection ? {
|
||
selectedRowKeys,
|
||
onChange: (newSelectedRowKeys: React.Key[]) => {
|
||
setSelectedRowKeys(newSelectedRowKeys);
|
||
}
|
||
} : undefined}
|
||
dataSource={fileList.data}
|
||
pagination={false}
|
||
scroll={{ y: '40vh' }}
|
||
style={{ width: '100%' }}
|
||
>
|
||
<Column title="文件" dataIndex="fileName" key="fileName" width={140} />
|
||
<Column title="更新时间" dataIndex="modifyTime" key="modifyTime" width={200} />
|
||
<Column title="大小" render={(item) => (
|
||
<>
|
||
<span>{item.size / 1024 > 1000 ? (item.size / (1024 * 1024)).toFixed(2) + 'MB' : (item.size / 1024).toFixed(2) + 'KB'}</span>
|
||
</>
|
||
)} />
|
||
<Column title="上传者" dataIndex="userName" key="userName" />
|
||
<Column title="下载次数"
|
||
render={(item) => (
|
||
<>
|
||
<span>{item.downloadCount}次</span>
|
||
</>
|
||
)}
|
||
/>
|
||
<Column title="操作" render={(item) => (
|
||
<>
|
||
<VerticalAlignBottomOutlined title='下载' style={{ color: '#5575F2', cursor: 'pointer' }} onClick={() => {
|
||
GetRoomFileDwUrl(item.fileUrl, item.id).then(res => {
|
||
if (res.code === 200) {
|
||
const downloadLink = document.createElement("a");
|
||
downloadLink.href = res.data;
|
||
downloadLink.download = item.fileName;
|
||
downloadLink.click();
|
||
getRoomFile()
|
||
}
|
||
})
|
||
}} />
|
||
{/* <FolderOutlined title='文件' style={{ color: '#FFA000', cursor: 'pointer' }} /> */}
|
||
</>
|
||
)} />
|
||
</Table>
|
||
<div style={{ display: 'flex', justifyContent: 'center', marginTop: '10px' }}>
|
||
<Pagination size="small" total={fileList.total} onChange={(e) => {
|
||
setFileList({
|
||
...fileList,
|
||
pageIndex: e
|
||
})
|
||
}} pageSize={fileList.pageSize} current={fileList.pageIndex} hideOnSinglePage={true} />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end' }}>
|
||
|
||
</div>
|
||
</div>
|
||
</Modal>
|
||
<SpeakerModeModal ref={speakerModeModalRef} />
|
||
<InvitingPersonnelModal ref={invitingPersonnelRef} />
|
||
</>
|
||
)
|
||
}
|
||
|
||
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.account ? styles.active : ''}`}>
|
||
暂无视频
|
||
</div>
|
||
</>
|
||
)
|
||
}
|
||
|
||
export default Meeting
|