From 950076b8f58e6e441c5c90a79cf034c1a30a9970 Mon Sep 17 00:00:00 2001 From: yj <1336058017@qq.com> Date: Thu, 18 Jul 2024 15:35:30 +0800 Subject: [PATCH] =?UTF-8?q?=E6=A8=A1=E5=BC=8F=E5=88=87=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SpeakerModeModal/index.module.scss | 137 ++++++++ src/components/SpeakerModeModal/index.tsx | 137 ++++++++ src/page/Meeting/index.module.scss | 315 +++++++++++------- src/page/Meeting/index.tsx | 127 +++++-- src/utils/package/agora.ts | 1 - src/utils/styles/main.css | 2 +- 6 files changed, 561 insertions(+), 158 deletions(-) create mode 100644 src/components/SpeakerModeModal/index.module.scss create mode 100644 src/components/SpeakerModeModal/index.tsx diff --git a/src/components/SpeakerModeModal/index.module.scss b/src/components/SpeakerModeModal/index.module.scss new file mode 100644 index 0000000..bf05079 --- /dev/null +++ b/src/components/SpeakerModeModal/index.module.scss @@ -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; + } + } + } +} \ No newline at end of file diff --git a/src/components/SpeakerModeModal/index.tsx b/src/components/SpeakerModeModal/index.tsx new file mode 100644 index 0000000..b5b1d0f --- /dev/null +++ b/src/components/SpeakerModeModal/index.tsx @@ -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 ( + <> + setIsSpeakerModeModal(false)} + centered + width={'30vw'} + > +
+ setMode('FreedomMode')} /> + setMode('StandardMode')} /> + setMode('SpeakerMode')} /> + setMode('SingleScreenMode')} /> + setMode('DualScreenMode')} /> + setMode('FourScreenMode')} /> +
+
+ + ) +}) + +const FreedomMode: React.FC = ({ onClick }) => { + // 自由者模式 + return ( + <> +
+
+ {[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16].map(item =>
)} +
+ 自由者模式 +
+ + ) +} +const StandardMode: React.FC = ({ onClick }) => { + // 标准模式 + return ( + <> +
+
+
+
+
+
+
+
+
+
+ 标准模式 +
+ + ) +} +const SpeakerMode: React.FC = ({ onClick }) => { + // 演讲者模式 + return ( + <> +
+
+
+
+
+
+
+
+
+
+ 演讲者模式 +
+ + ) +} +const SingleScreenMode: React.FC = ({ onClick }) => { + // 单画面模式 + return ( + <> +
+
+
+
+ 单画面模式 +
+ + ) +} +const DualScreenMode: React.FC = ({ onClick }) => { + // 二分屏模式 + return ( + <> +
+
+
+
+
+ 二分屏模式 +
+ + ) +} +const FourScreenMode: React.FC = ({ onClick }) => { + // 四分屏模式 + return ( + <> +
+
+
+
+
+
+
+ 四分屏模式 +
+ + ) +} + +export default SpeakerModeModal \ No newline at end of file diff --git a/src/page/Meeting/index.module.scss b/src/page/Meeting/index.module.scss index 60d9244..23077fc 100644 --- a/src/page/Meeting/index.module.scss +++ b/src/page/Meeting/index.module.scss @@ -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; } } diff --git a/src/page/Meeting/index.tsx b/src/page/Meeting/index.tsx index b71a32f..b40fe1a 100644 --- a/src/page/Meeting/index.tsx +++ b/src/page/Meeting/index.tsx @@ -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(); + const speakerModeModalRef = useRef(); 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 => { await GetRoomFile({ @@ -332,7 +336,6 @@ const Meeting: React.FC = () => { } }) } - // 获取房间用户 const getRoomUser = async (): Promise => { await GetRoomUser(state.channelId).then(res => { @@ -341,25 +344,30 @@ const Meeting: React.FC = () => { } }) } - const handleCustomStorageChange = async (e: any): Promise => { - 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 => { 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 ( <>
@@ -412,17 +471,17 @@ const Meeting: React.FC = () => {
会议号:{state.channelId}
-
演讲者模式
+
{getMeetingContentBodyLeftModeText()}
-
-
+
+
{roomUserList.map((item: any, index: number) =>
{ setCurrentVideoId(item.account) @@ -430,17 +489,22 @@ const Meeting: React.FC = () => { >
- + 暂无视频
{meetingContentUser(item)} {item.enableCamera ? null : meetingContentError()}
)} -
-
-
- {/* {meetingContentUser()} */} + {list.map(item => +
+
+
+ 暂无视频 +
+
+
+ )}
{ @@ -748,6 +812,7 @@ const Meeting: React.FC = () => {
+ ) } diff --git a/src/utils/package/agora.ts b/src/utils/package/agora.ts index af1b6bc..34ca4c7 100644 --- a/src/utils/package/agora.ts +++ b/src/utils/package/agora.ts @@ -228,7 +228,6 @@ const agora = { ); } }, - // 停止录制音视频 stopRecording: () => { iMediaRecorder.stopRecording() diff --git a/src/utils/styles/main.css b/src/utils/styles/main.css index d0c93c0..fc5348e 100644 --- a/src/utils/styles/main.css +++ b/src/utils/styles/main.css @@ -35,7 +35,7 @@ img { /* 修改垂直滚动条 */ ::-webkit-scrollbar { - width: 6px; + width: 10px; height: 10px; }