模式切换

This commit is contained in:
yj 2024-07-18 15:35:30 +08:00
parent 2dac39f63c
commit 950076b8f5
6 changed files with 561 additions and 158 deletions

View File

@ -0,0 +1,137 @@
.speakerModeModal {
max-height: 40vh;
overflow-y: auto;
display: flex;
align-content: flex-start;
flex-wrap: wrap;
>div {
cursor: pointer;
width: calc(100% / 3);
padding: 10px;
display: flex;
flex-direction: column;
align-items: center;
box-sizing: border-box;
>div {
height: 94px;
width: 144px;
}
>span {
color: white;
font-size: 20px;
margin-top: 10px;
}
}
.freedomMode {
>div {
display: flex;
flex-wrap: wrap;
>div {
width: calc(100% / 4);
height: calc(100% / 4);
border: 1px white solid;
box-sizing: border-box;
background-color: #101317;
}
}
}
.standardMode {
>div {
display: flex;
flex-direction: column;
>div:nth-child(1) {
display: flex;
align-items: center;
height: 25%;
>div {
height: 100%;
width: calc(100% / 4);
border: 1px white solid;
box-sizing: border-box;
background-color: #101317;
}
}
>div:nth-child(2) {
height: 75%;
border: 1px white solid;
box-sizing: border-box;
background-color: #101317;
}
}
}
.speakerMode {
>div {
display: flex;
>div:nth-child(1) {
display: flex;
flex-direction: column;
width: 25%;
>div {
width: 100%;
height: calc(100% / 4);
border: 1px white solid;
box-sizing: border-box;
background-color: #101317;
}
}
>div:nth-child(2) {
width: 75%;
border: 1px white solid;
box-sizing: border-box;
background-color: #101317;
}
}
}
.singleScreenMode {
>div {
border: 1px white solid;
box-sizing: border-box;
background-color: #101317;
}
}
.dualScreenMode {
>div {
display: flex;
>div {
width: 50%;
height: 100%;
border: 1px white solid;
box-sizing: border-box;
background-color: #101317;
}
}
}
.fourScreenMode {
>div {
display: flex;
flex-wrap: wrap;
>div {
width: 50%;
height: 50%;
border: 1px white solid;
box-sizing: border-box;
background-color: #101317;
}
}
}
}

View File

@ -0,0 +1,137 @@
import styles from '@/components/SpeakerModeModal/index.module.scss'
import { Button, message, Modal, Select, Slider } from 'antd';
import { useState, useImperativeHandle, forwardRef } from "react";
import { storage } from '@/utils';
interface Props {
onClick: () => void;
}
const SpeakerModeModal = forwardRef((props: any, ref: any) => {
useImperativeHandle(ref, () => ({
changeSpeakerMode: () => {
setIsSpeakerModeModal(true)
}
}))
const [isSpeakerModeModal, setIsSpeakerModeModal] = useState(false);
const setMode = (mode: string): void => {
storage.setItem('meetingMode', mode)
setIsSpeakerModeModal(false)
}
return (
<>
<Modal
title="演讲者模式"
open={isSpeakerModeModal}
footer={null}
onCancel={() => setIsSpeakerModeModal(false)}
centered
width={'30vw'}
>
<div className={styles.speakerModeModal}>
<FreedomMode onClick={() => setMode('FreedomMode')} />
<StandardMode onClick={() => setMode('StandardMode')} />
<SpeakerMode onClick={() => setMode('SpeakerMode')} />
<SingleScreenMode onClick={() => setMode('SingleScreenMode')} />
<DualScreenMode onClick={() => setMode('DualScreenMode')} />
<FourScreenMode onClick={() => setMode('FourScreenMode')} />
</div>
</Modal>
</>
)
})
const FreedomMode: React.FC<Props> = ({ onClick }) => {
// 自由者模式
return (
<>
<div className={styles.freedomMode} onClick={onClick}>
<div>
{[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16].map(item => <div key={item}></div>)}
</div>
<span></span>
</div>
</>
)
}
const StandardMode: React.FC<Props> = ({ onClick }) => {
// 标准模式
return (
<>
<div className={styles.standardMode} onClick={onClick}>
<div>
<div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
<div></div>
</div>
<span></span>
</div>
</>
)
}
const SpeakerMode: React.FC<Props> = ({ onClick }) => {
// 演讲者模式
return (
<>
<div className={styles.speakerMode} onClick={onClick}>
<div>
<div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
<div></div>
</div>
<span></span>
</div>
</>
)
}
const SingleScreenMode: React.FC<Props> = ({ onClick }) => {
// 单画面模式
return (
<>
<div className={styles.singleScreenMode} onClick={onClick}>
<div>
<div></div>
</div>
<span></span>
</div>
</>
)
}
const DualScreenMode: React.FC<Props> = ({ onClick }) => {
// 二分屏模式
return (
<>
<div className={styles.dualScreenMode} onClick={onClick}>
<div>
<div></div>
<div></div>
</div>
<span></span>
</div>
</>
)
}
const FourScreenMode: React.FC<Props> = ({ onClick }) => {
// 四分屏模式
return (
<>
<div className={styles.fourScreenMode} onClick={onClick}>
<div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
<span></span>
</div>
</>
)
}
export default SpeakerModeModal

View File

@ -1,3 +1,63 @@
@mixin meetingContent () {
.meetingContentUser {
position: absolute;
left: 20px;
bottom: 20px;
display: flex;
align-items: center;
z-index: 2;
.meetingContentUserRole {
background: #FDC229;
border-radius: 6px;
width: 44px;
height: 34px;
display: flex;
justify-content: center;
align-items: center;
margin-right: 6px;
>img {
width: 20px;
}
}
.meetingContentUserName {
display: flex;
align-items: center;
background-color: #0000009E;
border-radius: 6px;
height: 34px;
padding: 0 4px;
>img {
width: 28px;
}
>span {
font-size: 18px;
color: #EEEEEE;
margin-left: 4px;
}
}
}
.meetingContentError {
position: absolute;
justify-content: center;
display: flex;
align-items: center;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 1;
background-color: black;
color: white;
font-size: 20px;
}
}
.meeting {
width: 100%;
height: 100%;
@ -84,129 +144,132 @@
.meetingContentBody {
flex-grow: 1;
height: 0;
display: flex;
align-items: flex-start;
.meetingContentBodyLeft {
height: 100%;
width: 0px;
flex-grow: 1;
display: flex;
flex-direction: column;
padding-bottom: 18px;
height: 100%;
box-sizing: border-box;
position: relative;
.meetingContentSwiper {
margin: 20px 0 12px;
flex-shrink: 0;
display: flex;
align-items: center;
overflow-x: scroll;
padding: 0 20px;
.meetingContentSwiperCard {
height: 196px;
width: 280px;
border-radius: 10px;
overflow: hidden;
position: relative;
cursor: pointer;
margin-right: 20px;
flex-shrink: 0;
.meetingContentSwiperCardVdeio {
width: 100%;
height: 100%;
background: black;
position: relative;
.meetingContentSwiperCardVdeioLoading {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
}
&:last-child {
margin-right: 0;
}
}
.active {
border: 1px white solid;
}
}
.meetingContentVideo {
flex-grow: 1;
position: relative;
width: 1378px;
margin: 0 auto;
height: 0px;
.meetingContentVideoDom {
width: 100%;
height: 100%;
background: black;
}
}
.meetingContentUser {
position: absolute;
left: 10px;
bottom: 10px;
display: flex;
align-items: center;
z-index: 2;
.meetingContentUserRole {
background: #FDC229;
border-radius: 6px;
width: 44px;
height: 34px;
display: flex;
justify-content: center;
align-items: center;
margin-right: 6px;
>img {
width: 20px;
}
}
.meetingContentUserName {
display: flex;
align-items: center;
background-color: #0000009E;
border-radius: 6px;
height: 34px;
padding: 0 4px;
>img {
width: 28px;
}
>span {
color: #EEEEEE;
margin-left: 4px;
}
}
}
.meetingContentError {
position: absolute;
justify-content: center;
display: flex;
align-items: center;
left: 0;
top: 0;
// 自由者模式
.meetingContentBodyLeftFreedomMode {
width: 100%;
height: 100%;
z-index: 1;
background-color: black;
color: white;
font-size: 20px;
display: flex;
flex-wrap: wrap;
overflow-y: auto;
}
// 标准模式
.meetingContentBodyLeftStandardMode {
display: flex;
overflow-x: auto;
}
// 演讲者模式
.meetingContentBodyLeftSpeakerMode {
width: 25%;
overflow-y: auto;
height: 100%;
.meetingContentSwiperCard {
width: 100%;
}
}
// 单画面模式
.meetingContentBodyLeftSingleScreenMode {
width: 100%;
height: 100%;
display: flex;
flex-wrap: wrap;
overflow-y: auto;
.meetingContentSwiperCard {
width: 100% !important;
height: 100% !important;
}
}
// 二分屏模式
.meetingContentBodyLeftDualScreenMode {
width: 100%;
height: 100%;
display: flex;
flex-wrap: wrap;
overflow-y: auto;
.meetingContentSwiperCard {
width: 50% !important;
height: 100% !important;
}
}
// 四分屏模式
.meetingContentBodyLeftFourScreenMode {
width: 100%;
height: 100%;
display: flex;
flex-wrap: wrap;
overflow-y: auto;
.meetingContentSwiperCard {
width: 50% !important;
height: 50% !important;
}
}
// 标准模式视频
.meetingContentSwiperCardStandardMode {
position: absolute !important;
bottom: 0;
left: 0;
height: calc(100% - 300px) !important;
width: 100% !important;
}
// 演讲者模式视频
.meetingContentSwiperCardSpeakerMode {
position: absolute !important;
right: 0;
bottom: 0;
height: 100% !important;
width: calc(100% - 25%) !important;
}
.meetingContentSwiperCard {
height: 300px;
width: calc(100% / 4);
border-radius: 10px;
overflow: hidden;
position: relative;
flex-shrink: 0;
padding: 10px;
box-sizing: border-box;
.meetingContentSwiperCardVdeio {
width: 100%;
height: 100%;
cursor: pointer;
background: black;
position: relative;
.meetingContentSwiperCardVdeioLoading {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
font-size: 26px;
color: white;
}
}
}
@include meetingContent()
}
.meetingContentBodyRight {
@ -231,7 +294,7 @@
>span {
color: #EEEEEE;
font-size: 20px;
font-size: 24px;
}
>img {
@ -256,14 +319,14 @@
align-items: center;
>span {
font-size: 14px;
font-size: 20px;
color: #F3F3F5;
margin-left: 4px;
}
>div {
width: 36px;
height: 36px;
width: 40px;
height: 40px;
overflow: hidden;
border-radius: 50%;
@ -280,7 +343,7 @@
align-items: center;
>img {
width: 17px;
width: 28px;
margin-left: 4px;
}
}
@ -331,7 +394,7 @@
>span {
color: #EEEEEE;
font-size: 20px;
font-size: 24px;
}
>img {
@ -357,14 +420,14 @@
align-items: center;
>span {
font-size: 14px;
font-size: 20px;
color: #F3F3F5;
margin-left: 4px;
}
>div {
width: 36px;
height: 36px;
width: 40px;
height: 40px;
overflow: hidden;
border-radius: 50%;
@ -384,6 +447,7 @@
box-sizing: border-box;
border-radius: 0 25px 25px 25px;
margin: 10px 0 10px 40px;
font-size: 20px;
}
}
@ -398,14 +462,14 @@
flex-direction: row-reverse;
>span {
font-size: 14px;
font-size: 20px;
color: #F3F3F5;
}
>div {
margin-left: 4px;
width: 36px;
height: 36px;
width: 40px;
height: 40px;
overflow: hidden;
border-radius: 50%;
@ -425,6 +489,7 @@
box-sizing: border-box;
border-radius: 25px 0 25px 25px;
margin: 10px 40px 10px 0;
font-size: 20px;
}
}

View File

@ -2,7 +2,7 @@ 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, LoadingOutlined, ProfileOutlined, ReloadOutlined, SearchOutlined, VerticalAlignBottomOutlined } from '@ant-design/icons';
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';
@ -11,6 +11,7 @@ import axios from 'axios';
import ImageUrl from '@/utils/package/imageUrl'
import agora from '@/utils/package/agora'
import StupWizard from '@/components/StupWizard';
import SpeakerModeModal from '@/components/SpeakerModeModal';
import { onInvoke, onSignalr } from '@/utils/package/signalr';
import dayjs from 'dayjs';
import durationPlugin from 'dayjs/plugin/duration';
@ -21,6 +22,7 @@ const Meeting: React.FC = () => {
const navigate = useNavigate();
const { state } = useLocation();
const stupWizardRef = useRef<any>();
const speakerModeModalRef = useRef<any>();
const [statusList, setStatusList] = useState({
userList: false,
userChatList: false,
@ -112,10 +114,13 @@ const Meeting: React.FC = () => {
let [currentSeconds, setCurrentSeconds] = useState(0)
const [currentEffective, setCurrentEffective] = useState(0)
const [open, setOpen] = useState(false)
const [meetingMode, setMeetingMode] = useState('')
const [list] = useState([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20])
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.setCameraCapture(VideoSourceType.VideoSourceCameraPrimary)
agora.setJoinChannel({
@ -309,7 +314,6 @@ const Meeting: React.FC = () => {
}
})
};
// 获取共享文件列表
const getRoomFile = async (): Promise<void> => {
await GetRoomFile({
@ -332,7 +336,6 @@ const Meeting: React.FC = () => {
}
})
}
// 获取房间用户
const getRoomUser = async (): Promise<void> => {
await GetRoomUser(state.channelId).then(res => {
@ -341,25 +344,30 @@ const Meeting: React.FC = () => {
}
})
}
const handleCustomStorageChange = async (e: any): Promise<void> => {
if (e.key === 'isJoin') {
if (e.value) {
await onInvoke('joinChannel', {
roomNum: state.channelId,
enableMicr: true,
enableCamera: true
})
getRoomUser()
} else {
onInvoke('levelChannel', {
roomNum: state.channelId
})
}
} else if (e.key === 'isRemotJoin') {
setTimeout(() => {
getRoomUser()
}, 1000)
switch (e.key) {
case 'isJoin':
if (e.value) {
await onInvoke('joinChannel', {
roomNum: state.channelId,
enableMicr: true,
enableCamera: true
})
getRoomUser()
} else {
onInvoke('levelChannel', {
roomNum: state.channelId
})
}
break;
case 'isRemotJoin':
setTimeout(() => {
getRoomUser()
}, 1000)
break;
case 'meetingMode':
setMeetingMode(e.value)
break;
}
};
// 聊天发送
@ -379,7 +387,6 @@ const Meeting: React.FC = () => {
message.success('请输入内容!')
}
}
// 开关麦克风
const postOpenMicr = async (enableMicr: boolean, isAll?: boolean): Promise<void> => {
await PostOpenMicr({
@ -397,6 +404,58 @@ const Meeting: React.FC = () => {
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}>
@ -412,17 +471,17 @@ const Meeting: React.FC = () => {
</div>
<div>{state.channelId}</div>
<div className='drag'>
<div className={styles.meetingGrayButton}></div>
<div className={styles.meetingGrayButton} onClick={changeSpeakerMode}>{getMeetingContentBodyLeftModeText()}</div>
<Operation></Operation>
</div>
</div>
<div className={styles.meetingContent}>
<div className={styles.meetingContentBody}>
<div className={styles.meetingContentBodyLeft}>
<div className={`${styles.meetingContentSwiper} drag`}>
<div className={`${styles.meetingContentBodyLeft} drag`}>
<div className={getMeetingContentBodyLeftModeClass()} >
{roomUserList.map((item: any, index: number) =>
<div
className={`${styles.meetingContentSwiperCard} ${item.account === currentVideoId ? styles.active : ''}`}
className={`${styles.meetingContentSwiperCard} ${setMeetingContentSwiperCardClass(item.account)}`}
key={index}
onClick={() => {
setCurrentVideoId(item.account)
@ -430,17 +489,22 @@ const Meeting: React.FC = () => {
>
<div className={styles.meetingContentSwiperCardVdeio} id={`video-${item.account}`}>
<div className={styles.meetingContentSwiperCardVdeioLoading}>
<LoadingOutlined style={{ color: 'white', fontSize: '30px' }} />
</div>
</div>
{meetingContentUser(item)}
{item.enableCamera ? null : meetingContentError()}
</div>
)}
</div>
<div className={`${styles.meetingContentVideo} drag`}>
<div className={styles.meetingContentVideoDom}></div>
{/* {meetingContentUser()} */}
{list.map(item =>
<div className={styles.meetingContentSwiperCard} key={item}>
<div className={styles.meetingContentSwiperCardVdeio}>
<div className={styles.meetingContentSwiperCardVdeioLoading}>
</div>
</div>
</div>
)}
</div>
</div>
{
@ -748,6 +812,7 @@ const Meeting: React.FC = () => {
</div>
</div>
</Modal>
<SpeakerModeModal ref={speakerModeModalRef} />
</>
)
}

View File

@ -228,7 +228,6 @@ const agora = {
);
}
},
// 停止录制音视频
stopRecording: () => {
iMediaRecorder.stopRecording()

View File

@ -35,7 +35,7 @@ img {
/* 修改垂直滚动条 */
::-webkit-scrollbar {
width: 6px;
width: 10px;
height: 10px;
}