master #25

Merged
yangqiang merged 3 commits from master into yangjie 2024-10-29 09:33:10 +08:00
34 changed files with 2372 additions and 413 deletions
Showing only changes of commit d536e5f100 - Show all commits

179
main.js
View File

@ -52,23 +52,6 @@ class AppWindow extends BrowserWindow {
}); });
} }
} }
function showWindow() {
// 如果主窗口已经存在但被最小化了,则恢复显示
if (mainWindow && mainWindow.isMinimized()) {
mainWindow.show();
}
// 如果主窗口已存在但不是焦点窗口,则将其置为焦点
if (mainWindow && !mainWindow.isFocused()) {
mainWindow.show();
mainWindow.focus();
}
// 如果主窗口还没有被创建,则创建它
if (!mainWindow) {
createWindow();
}
}
function quit() { function quit() {
app.quit() app.quit()
} }
@ -80,7 +63,7 @@ function createTray() {
const contextMenu = Menu.buildFromTemplate([ const contextMenu = Menu.buildFromTemplate([
{ {
label: '打开', click: () => { label: '打开', click: () => {
showWindow() mainWindow.webContents.send('isOpenWindows');
}, },
// icon: iconPath, // icon: iconPath,
}, },
@ -100,11 +83,7 @@ function createTray() {
tray.setToolTip('智汇享'); tray.setToolTip('智汇享');
tray.setContextMenu(contextMenu); tray.setContextMenu(contextMenu);
tray.on('click', () => { tray.on('click', () => {
if (mainWindow.isVisible()) { mainWindow.webContents.send('isOpenWindows');
mainWindow.hide()
} else {
mainWindow.show()
}
}); });
} }
@ -113,11 +92,6 @@ function createWindow() {
mainWindow.focus(); mainWindow.focus();
} }
const additionalData = { myKey: 'myValue' } const additionalData = { myKey: 'myValue' }
// 退出房间
app.on('will-quit', async (event) => {
await mainWindow.webContents.send('quitAndInstall');
});
app.on('ready', () => { app.on('ready', () => {
const gotTheLock = app.requestSingleInstanceLock(additionalData) const gotTheLock = app.requestSingleInstanceLock(additionalData)
if (gotTheLock) { if (gotTheLock) {
@ -161,7 +135,6 @@ app.on('ready', () => {
mainWindow.on('move', () => { mainWindow.on('move', () => {
// 如果是全屏自动恢复到上次窗口大小 // 如果是全屏自动恢复到上次窗口大小
if (isMaximized) { if (isMaximized) {
mainWindow.setResizable(true)
mainWindow.unmaximize() mainWindow.unmaximize()
isMaximized = false; isMaximized = false;
} }
@ -177,10 +150,8 @@ app.on('ready', () => {
break; break;
case 'maximize': case 'maximize':
mainWindow.maximize() mainWindow.maximize()
mainWindow.setResizable(false)
break; break;
case 'unmaximize': case 'unmaximize':
mainWindow.setResizable(true)
mainWindow.unmaximize() mainWindow.unmaximize()
break; break;
case 'minimize': case 'minimize':
@ -189,6 +160,10 @@ app.on('ready', () => {
case 'hide': case 'hide':
mainWindow.hide() mainWindow.hide()
break; break;
case 'show':
mainWindow.show()
mainWindow.focus();
break;
} }
}); });
// 导出是否全屏 // 导出是否全屏
@ -199,6 +174,10 @@ app.on('ready', () => {
ipcMain.handle('getVersion', () => { ipcMain.handle('getVersion', () => {
return app.getVersion(); return app.getVersion();
}); });
// 获取窗口是否显示
ipcMain.handle('isVisible', () => {
return mainWindow.isVisible();
});
// 获取共享屏幕列表 // 获取共享屏幕列表
ipcMain.handle('getSources', async () => { ipcMain.handle('getSources', async () => {
return await desktopCapturer.getSources({ return await desktopCapturer.getSources({
@ -210,9 +189,12 @@ app.on('ready', () => {
clipboard.writeText(text) clipboard.writeText(text)
}); });
// 退出 // 退出
ipcMain.handle('quit', async (event) => { ipcMain.handle('quit', async (event, bool) => {
await mainWindow.webContents.send('quitAndInstall'); if (bool) {
quit() quit()
} else {
await mainWindow.webContents.send('quitAndInstall');
}
}); });
// 加入房间通知 // 加入房间通知
ipcMain.handle('joinNotification', (event, user) => { ipcMain.handle('joinNotification', (event, user) => {
@ -273,10 +255,7 @@ app.on('ready', () => {
// 设置窗口尺寸 // 设置窗口尺寸
mainWindow.setSize(config.width, config.height) mainWindow.setSize(config.width, config.height)
// 设置窗口位置使其居中于当前屏幕 // 设置窗口位置使其居中于当前屏幕
const display = screen.getDisplayMatching({ ...mainWindow.getBounds() }); mainWindowCenter()
const x = Math.round((display.workArea.width - mainWindow.getSize()[0]) / 2);
const y = Math.round((display.workArea.height - mainWindow.getSize()[1]) / 2);
mainWindow.setPosition(x, y);
}); });
// 写入注册表 // 写入注册表
ipcMain.handle('setRegistry', (event, uuid) => { ipcMain.handle('setRegistry', (event, uuid) => {
@ -319,19 +298,90 @@ app.on('ready', () => {
width: config.width, width: config.width,
height: config.height, height: config.height,
}) })
if (env === 'development') {
// 开发
child.loadURL(config.url) child.loadURL(config.url)
} else {
// 测试 | 生产
child.loadURL(`file://${path.join(__dirname, './dist/index.html')}#/${config.key}`);
}
childWindow[config.key] = child childWindow[config.key] = child
child.once('ready-to-show', () => { child.once('ready-to-show', () => {
child.show() if (config.show) {
if (config.key === 'shareScreenWindow') { childWindow[config.key].show()
const display = screen.getDisplayMatching({ ...child.getBounds() });
const x = Math.round((display.workArea.width - child.getSize()[0]) / 2);
child.setPosition(x, 0);
child.setResizable(false)
child.setMovable(false)
mainWindow.setPosition(-999999, -999999);
} }
childWindow[config.key].setAlwaysOnTop(true, 'screen-saver')
childWindow[config.key].setSkipTaskbar(true)
windowOperation(config)
}) })
child.webContents.on('before-input-event', (event, input) => {
// if (env === 'development') {
if (input.key === 'F12') {
child.webContents.openDevTools()
}
// }
});
});
// 关闭子窗口
ipcMain.handle('closeChildWindow', (event, key) => {
if (key === 'shareScreenWindow') {
for (const k in childWindow) {
if (childWindow[k]) {
childWindow[k].close()
childWindow[k] = ""
}
}
mainWindowCenter()
} else {
childWindow[key].close()
childWindow[key] = ""
}
});
// 设置子窗口
ipcMain.handle('setChildWindow', (event, config) => {
switch (config.key) {
case 'shareScreenWindow':
childWindow[config.key].setBounds({ width: config.width })
break;
case 'chatSmallWindow':
childWindow[config.key].setBounds({ height: config.height })
break;
case 'noticeWindow':
childWindow[config.key].setBounds({ width: config.width, height: config.height })
break;
}
});
// 隐藏显示子窗口
ipcMain.handle('setChildWindowShow', (event, config) => {
if (config.key === 'noticeWindow') {
if (config.bool) {
childWindow[config.key].show()
} else {
if (childWindow[config.key].isVisible()) {
childWindow[config.key].hide()
}
}
} else {
if (childWindow[config.key].isVisible()) {
childWindow[config.key].hide()
} else {
childWindow[config.key].show()
}
}
});
// 隐藏主窗口
ipcMain.handle('mainWindowHide', () => {
mainWindowHide()
});
// 居中主窗口
ipcMain.handle('mainWindowCenter', () => {
mainWindowCenter()
});
// 窗口通信
ipcMain.handle('windowHandleMessage', (event, data) => {
if (childWindow[data.key]) {
childWindow[data.key].webContents.send('windowHandleMessageCallBack', data)
}
}); });
} }
}); });
@ -401,3 +451,42 @@ function cancleDownloadUpdate() {
function quitAndInstall() { function quitAndInstall() {
autoUpdater.quitAndInstall(); autoUpdater.quitAndInstall();
} }
function windowOperation(config) {
const child = childWindow[config.key];
const display = screen.getDisplayMatching({ ...child.getBounds() });
const { width, height } = display.size
let x, y;
child.setResizable(false)
switch (config.key) {
case 'shareScreenWindow':
x = Math.round((display.workArea.width - child.getSize()[0]) / 2);
child.setPosition(x, 0);
mainWindowHide()
break;
case 'chatSmallWindow':
y = height - child.getSize()[1];
child.setPosition(40, y - 200);
break;
case 'currentSpeakUserWindow':
x = width - child.getSize()[0];
child.setPosition(x - 40, 40);
break;
case 'noticeWindow':
x = width - child.getSize()[0];
y = height - child.getSize()[1];
child.setPosition(x, y - 80);
break;
}
}
// 主窗口居中
function mainWindowCenter() {
const display = screen.getDisplayMatching({ ...mainWindow.getBounds() });
const x = Math.round((display.workArea.width - mainWindow.getSize()[0]) / 2);
const y = Math.round((display.workArea.height - mainWindow.getSize()[1]) / 2);
mainWindow.setPosition(x, y);
}
// 主窗口隐藏
function mainWindowHide() {
mainWindow.setPosition(-999999, -999999);
}

View File

@ -21,6 +21,10 @@ window.electron = {
getVersion: () => { getVersion: () => {
return ipcRenderer.invoke('getVersion') return ipcRenderer.invoke('getVersion')
}, },
// 获取窗口是否显示
isVisible: () => {
return ipcRenderer.invoke('isVisible')
},
// 获取共享屏幕列表 // 获取共享屏幕列表
getSources: () => { getSources: () => {
return ipcRenderer.invoke('getSources') return ipcRenderer.invoke('getSources')
@ -38,8 +42,8 @@ window.electron = {
ipcRenderer.on('onQuit', callback) ipcRenderer.on('onQuit', callback)
}, },
// 退出房间 // 退出房间
quit: () => { quit: (bool) => {
return ipcRenderer.invoke('quit') return ipcRenderer.invoke('quit', bool)
}, },
// 监听更新 // 监听更新
onUpdate: (callback) => { onUpdate: (callback) => {
@ -49,6 +53,10 @@ window.electron = {
quitAndInstall: (callback) => { quitAndInstall: (callback) => {
ipcRenderer.on('quitAndInstall', callback) ipcRenderer.on('quitAndInstall', callback)
}, },
// 点击任务栏图标是否打开窗口
isOpenWindows: (callback) => {
ipcRenderer.on('isOpenWindows', callback)
},
// 通知下载最新的包 // 通知下载最新的包
onDownload: (type) => { onDownload: (type) => {
ipcRenderer.invoke('updateDownload', type) ipcRenderer.invoke('updateDownload', type)
@ -77,4 +85,32 @@ window.electron = {
createChildWindow: (config) => { createChildWindow: (config) => {
ipcRenderer.invoke('createChildWindow', config) ipcRenderer.invoke('createChildWindow', config)
}, },
// 关闭子窗口
closeChildWindow: (key) => {
ipcRenderer.invoke('closeChildWindow', key)
},
// 设置子窗口
setChildWindow: (config) => {
ipcRenderer.invoke('setChildWindow', config)
},
// 隐藏主窗口
setChildWindowShow: (config) => {
ipcRenderer.invoke('setChildWindowShow', config)
},
// 隐藏主窗口
mainWindowHide: () => {
ipcRenderer.invoke('mainWindowHide')
},
// 居中主窗口
mainWindowCenter: () => {
ipcRenderer.invoke('mainWindowCenter')
},
// 窗口通信传参
windowHandleMessage: (data) => {
ipcRenderer.invoke('windowHandleMessage', data)
},
// 窗口通信回调
windowHandleMessageCallBack: (callback) => {
ipcRenderer.on('windowHandleMessageCallBack', callback)
},
} }

View File

@ -18,7 +18,13 @@ import { agora } from "@/utils/package/agora";
import QuitTips from "@/components/QuitTips"; import QuitTips from "@/components/QuitTips";
import { GetLeave } from "@/api/Meeting"; import { GetLeave } from "@/api/Meeting";
import path from "path"; import path from "path";
import ShareScreenWindow from "./page/ShareScreenWindow"; import ShareScreenWindow from "@/page/Meeting/ShareScreenWindow";
import UserListWindow from "@/page/Meeting/UserListWindow";
import ChatSmallWindow from "@/page/Meeting/ChatSmallWindow";
import ChatBigWindow from "@/page/Meeting/ChatBigWindow";
import CurrentSpeakUserWindow from "@/page/Meeting/CurrentSpeakUserWindow";
import NoticeWindow from "@/page/Meeting/NoticeWindow";
import { getKeyOpenChildWindow, setKeyOpenChildWindow } from "./utils/package/public";
const fs = require('fs').promises; const fs = require('fs').promises;
const { exec } = require('child_process'); const { exec } = require('child_process');
const App: React.FC = () => { const App: React.FC = () => {
@ -33,7 +39,8 @@ const App: React.FC = () => {
}); });
const [spinning, setSpinning] = useState(false); const [spinning, setSpinning] = useState(false);
const [isState, setIsState] = useState(true); const [isState, setIsState] = useState(true);
if (location.hash.indexOf('shareScreenWindow') == -1) { const urlHashArr = ['#/userListWindow', '#/shareScreenWindow', '#/chatSmallWindow', '#/chatBigWindow', '#/currentSpeakUserWindow', '#/noticeWindow']
if (urlHashArr.indexOf(location.hash) == -1) {
useEffect(() => { useEffect(() => {
let userInfo = JSON.parse(storage.getItem('user') as string) let userInfo = JSON.parse(storage.getItem('user') as string)
let loginInfo = JSON.parse(storage.getItem('login') as string) let loginInfo = JSON.parse(storage.getItem('login') as string)
@ -97,7 +104,22 @@ const App: React.FC = () => {
storage.setItem('setting', JSON.stringify(setting)) storage.setItem('setting', JSON.stringify(setting))
}) })
window.electron.quitAndInstall(async (_e: any) => { window.electron.quitAndInstall(async (_e: any) => {
leaveChannel() let bool = await window.electron.isVisible()
if (bool) {
storage.setItem('quitMeeting', true)
window.electron.setViewStatus('show')
}
})
window.electron.isOpenWindows(async (_e: any) => {
let bool = await window.electron.isVisible()
if (location.hash.indexOf('/meeting') === -1) {
window.electron.setViewStatus(bool ? 'hide' : 'show')
} else {
let shareScreenWindow = await getKeyOpenChildWindow('shareScreenWindow')
if (!shareScreenWindow) {
window.electron.setViewStatus(bool ? 'hide' : 'show')
}
}
}) })
}, []) }, [])
useEffect(() => { useEffect(() => {
@ -120,22 +142,26 @@ const App: React.FC = () => {
closeSetting: 'hide', //关闭按钮设置 closeSetting: 'hide', //关闭按钮设置
isAINoiseReduction: true, //是否开启ai降噪 isAINoiseReduction: true, //是否开启ai降噪
aINoiseReduction: 1, // 降噪模式 aINoiseReduction: 1, // 降噪模式
isRecordingTips: true, //是否开启录制提示
})) }))
} }
if (!storage.getItem('openChildWindow')) {
storage.setItem('openChildWindow', JSON.stringify({}))
}
}, []) }, [])
useEffect(() => { useEffect(() => {
if (isState) { if (isState) {
setIsState(false) setIsState(false)
window.electron.onQuit(async () => { window.electron.onQuit(async () => {
if (location.hash.indexOf('/login') === 1) { if (location.hash.indexOf('/login') === 1) {
window.electron.quit() window.electron.quit(location.hash.indexOf('/meeting') === -1)
} else { } else {
if (storage.getItem('isTips') === 'true') { if (storage.getItem('isTips') === 'true') {
const setting = JSON.parse(storage.getItem('setting') as string) const setting = JSON.parse(storage.getItem('setting') as string)
if (setting.closeSetting === 'hide') { if (setting.closeSetting === 'hide') {
window.electron.setViewStatus(setting.closeSetting) window.electron.setViewStatus(setting.closeSetting)
} else { } else {
window.electron.quit() window.electron.quit(location.hash.indexOf('/meeting') === -1)
} }
} else { } else {
quitTipsRef.current.changeModal() quitTipsRef.current.changeModal()
@ -208,6 +234,20 @@ const App: React.FC = () => {
}; };
const leaveChannel = async (bool?: boolean): Promise<void> => { const leaveChannel = async (bool?: boolean): Promise<void> => {
if (location.hash.indexOf('/meeting') === 1) { if (location.hash.indexOf('/meeting') === 1) {
window.electron.closeChildWindow('shareScreenWindow')
setKeyOpenChildWindow('shareScreenWindow', false)
window.electron.setViewStatus('show')
window.electron.getWindowSize().then((res: any) => {
window.electron.setMainWindowSize({
width: Math.ceil(res.width / 1.5),
height: Math.ceil(res.height / 1.3),
})
window.electron.getIsMaximized().then((b: boolean) => {
if (!b) {
window.electron.setViewStatus('maximize')
}
})
})
const data = JSON.parse(localStorage.stateInfo); const data = JSON.parse(localStorage.stateInfo);
if (!bool) { if (!bool) {
await GetLeave({ await GetLeave({
@ -243,6 +283,11 @@ const App: React.FC = () => {
<Route path='/login' element={<Login />} /> <Route path='/login' element={<Login />} />
<Route path='/meeting' element={<Meeting />} /> <Route path='/meeting' element={<Meeting />} />
<Route path='/shareScreenWindow' element={<ShareScreenWindow />} /> <Route path='/shareScreenWindow' element={<ShareScreenWindow />} />
<Route path='/userListWindow' element={<UserListWindow />} />
<Route path='/chatSmallWindow' element={<ChatSmallWindow />} />
<Route path='/chatBigWindow' element={<ChatBigWindow />} />
<Route path='/currentSpeakUserWindow' element={<CurrentSpeakUserWindow />} />
<Route path='/noticeWindow' element={<NoticeWindow />} />
<Route path='*' element={<NotFound />} /> <Route path='*' element={<NotFound />} />
</Routes> </Routes>
<Spin spinning={spinning} fullscreen /> <Spin spinning={spinning} fullscreen />

BIN
src/assets/icon50.png Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 430 B

After

Width:  |  Height:  |  Size: 430 B

BIN
src/assets/icon51.png Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 349 B

After

Width:  |  Height:  |  Size: 349 B

View File

@ -1,15 +1,21 @@
import styles from '@/components/EquipmentManagement/index.module.scss' import styles from '@/components/EquipmentManagement/index.module.scss'
import { getKeyOpenChildWindow } from '@/utils/package/public';
import { onInvoke } from '@/utils/package/signalr'; import { onInvoke } from '@/utils/package/signalr';
import { Button, Modal, Select, Slider, message } from 'antd'; import { Button, Modal, Select, Slider, message } from 'antd';
import { useState, useImperativeHandle, forwardRef } from "react"; import { useState, useImperativeHandle, forwardRef } from "react";
const EquipmentManagement = forwardRef((_props: any, ref: any) => { const EquipmentManagement = forwardRef((props: any, ref: any) => {
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
changeModal: async (uid: string, userName: string) => { changeModal: async (uid: string, userName: string) => {
setCallerUid(uid) setCallerUid(uid)
setDeviceInfo({}) setDeviceInfo({})
let isOpen = await getKeyOpenChildWindow('shareScreenWindow')
if (isOpen) {
props.getDriver?.(uid)
} else {
await onInvoke('getDrivers', { await onInvoke('getDrivers', {
uid uid
}) })
}
setUserName(userName) setUserName(userName)
setEquipmentManagementModal(true) setEquipmentManagementModal(true)
}, },
@ -21,6 +27,9 @@ const EquipmentManagement = forwardRef((_props: any, ref: any) => {
const [callerUid, setCallerUid] = useState('') const [callerUid, setCallerUid] = useState('')
const [deviceInfo, setDeviceInfo] = useState<any>({}) const [deviceInfo, setDeviceInfo] = useState<any>({})
const [userName, setUserName] = useState<any>({}) const [userName, setUserName] = useState<any>({})
const handleWindowsChange = async (): Promise<void> => {
setEquipmentManagementModal(false)
}
return ( return (
<> <>
<Modal <Modal
@ -30,7 +39,7 @@ const EquipmentManagement = forwardRef((_props: any, ref: any) => {
centered centered
width={'500px'} width={'500px'}
onCancel={() => { onCancel={() => {
setEquipmentManagementModal(false) handleWindowsChange()
}}> }}>
<div className={styles.equipmentManagement}> <div className={styles.equipmentManagement}>
<div> <div>
@ -83,16 +92,24 @@ const EquipmentManagement = forwardRef((_props: any, ref: any) => {
</div> </div>
<div> <div>
<Button type="primary" className='m-ant-btn' onClick={async () => { <Button type="primary" className='m-ant-btn' onClick={async () => {
let isOpen = await getKeyOpenChildWindow('shareScreenWindow')
if (isOpen) {
props.setDriver?.({
uid: callerUid,
driversJsonString: JSON.stringify(deviceInfo)
})
} else {
await onInvoke('setDrivers', { await onInvoke('setDrivers', {
uid: callerUid, uid: callerUid,
driversJsonString: JSON.stringify(deviceInfo) driversJsonString: JSON.stringify(deviceInfo)
}) })
setEquipmentManagementModal(false) }
handleWindowsChange()
message.success('设置成功') message.success('设置成功')
}}></Button> }}></Button>
<Button type="primary" <Button type="primary"
style={{ backgroundColor: '#31353A', marginLeft: '10px' }} style={{ backgroundColor: '#31353A', marginLeft: '10px' }}
onClick={() => setEquipmentManagementModal(false)}></Button> onClick={() => handleWindowsChange()}></Button>
</div> </div>
</div> </div>
</Modal > </Modal >

View File

@ -12,20 +12,26 @@ import { role } from '@/config/role';
let time = null as any; let time = null as any;
const JoinSetting = forwardRef((_props: any, ref: any) => { const JoinSetting = forwardRef((_props: any, ref: any) => {
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
changeModal: (roomNum: string = '') => { changeModal: async (roomNum: string = '') => {
let userInfo = JSON.parse(storage.getItem('user') as string) let userInfo = JSON.parse(storage.getItem('user') as string)
setUser(userInfo) setUser(userInfo)
setJoinRoomSettingModal(true) setJoinRoomSettingModal(true)
if (location.hash.indexOf('/meeting') === -1) {
await agora.init()
}
setJoinRoomSettingForm((res: any) => { setJoinRoomSettingForm((res: any) => {
res.forEach((item: any) => { res.forEach(async (item: any, index: number) => {
if (index === 0 && role.ID.includes(userInfo.roleId)) {
await agora.getAudioMediaList().then(res => {
item.active = res.ecordingList.length ? true : false
})
} else {
item.active = false item.active = false
}
}); });
return res return res
}) })
setRoomNumber(roomNum) setRoomNumber(roomNum)
if (location.hash.indexOf('/meeting') === -1) {
agora.init()
}
getDeviceList() getDeviceList()
} }
})) }))

View File

@ -1,7 +1,7 @@
import styles from '@/components/Operation/index.module.scss' import styles from '@/components/Operation/index.module.scss'
import ImageUrl from '@/utils/package/imageUrl'; import ImageUrl from '@/utils/package/imageUrl';
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
type OperationKeyType = 'minimize' | 'quit' | 'maximize' | 'unmaximize' | 'hide'; type OperationKeyType = 'minimize' | 'quit' | 'maximize' | 'unmaximize' | 'hide' | 'show';
type OperationType = { type OperationType = {
icon: string; icon: string;
key: OperationKeyType; key: OperationKeyType;
@ -46,7 +46,11 @@ const Operation: React.FC = () => {
key: 'quit', key: 'quit',
title: '关闭', title: '关闭',
onClick: (key: OperationKeyType) => { onClick: (key: OperationKeyType) => {
if (location.hash.indexOf('/meeting') === -1) {
window.electron.setViewStatus(key) window.electron.setViewStatus(key)
} else {
window.electron.quit(false)
}
}, },
show: true, show: true,
},]) },])

View File

@ -3,7 +3,7 @@ import { storage } from '@/utils';
import { InfoCircleOutlined } from '@ant-design/icons'; import { InfoCircleOutlined } from '@ant-design/icons';
import { Button, Checkbox, Modal, Radio } from 'antd'; import { Button, Checkbox, Modal, Radio } from 'antd';
import { useState, useImperativeHandle, forwardRef } from "react"; import { useState, useImperativeHandle, forwardRef } from "react";
type OperationKeyType = 'minimize' | 'quit' | 'maximize' | 'unmaximize' | 'hide'; type OperationKeyType = 'minimize' | 'quit' | 'maximize' | 'unmaximize' | 'hide' | 'show';
const QuitTips = forwardRef((props: any, ref: any) => { const QuitTips = forwardRef((props: any, ref: any) => {
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
changeModal: () => { changeModal: () => {
@ -52,7 +52,7 @@ const QuitTips = forwardRef((props: any, ref: any) => {
<Button type="primary" className='m-ant-btn' onClick={() => { <Button type="primary" className='m-ant-btn' onClick={() => {
setIsCloseModal(false) setIsCloseModal(false)
if (optionsValue === 'quit') { if (optionsValue === 'quit') {
window.electron.quit() window.electron.quit(location.hash.indexOf('/meeting') === -1)
} else { } else {
window.electron.setViewStatus(optionsValue) window.electron.setViewStatus(optionsValue)
} }

View File

@ -176,6 +176,9 @@
} }
.recordingComponents { .recordingComponents {
>div {
margin-bottom: 20px;
>span { >span {
color: #bfbfbf; color: #bfbfbf;
font-size: 16px; font-size: 16px;
@ -191,6 +194,7 @@
white-space: nowrap; white-space: nowrap;
} }
} }
}
} }
.fileComponents { .fileComponents {

View File

@ -95,7 +95,7 @@ const StupWizard = forwardRef((props: any, ref: any) => {
top: '16px', top: '16px',
cursor: 'pointer' cursor: 'pointer'
}} }}
onClick={() => { onClick={async () => {
if (location.hash.indexOf('/meeting') === -1) { if (location.hash.indexOf('/meeting') === -1) {
agora.release() agora.release()
} }
@ -459,6 +459,7 @@ const AudioComponents = () => {
} }
const RecordingComponents = () => { const RecordingComponents = () => {
const [filePath, setFilePath] = useState('') const [filePath, setFilePath] = useState('')
const [isRecordingTips, setIsRecordingTips] = useState(false)
const setting = JSON.parse(storage.getItem('setting') as string) const setting = JSON.parse(storage.getItem('setting') as string)
useEffect(() => { useEffect(() => {
if (!setting.recordingFilesPath) { if (!setting.recordingFilesPath) {
@ -467,10 +468,14 @@ const RecordingComponents = () => {
const parentDirectory = path.resolve(currentDirectory, '../../Downloads') + '\\'; const parentDirectory = path.resolve(currentDirectory, '../../Downloads') + '\\';
setting.recordingFilesPath = parentDirectory; setting.recordingFilesPath = parentDirectory;
setFilePath(setting.recordingFilesPath) setFilePath(setting.recordingFilesPath)
storage.setItem('setting', JSON.stringify(setting))
} else { } else {
setFilePath(setting.recordingFilesPath); setFilePath(setting.recordingFilesPath);
} }
if (setting.isRecordingTips === undefined) {
setting.isRecordingTips = true;
}
storage.setItem('setting', JSON.stringify(setting))
setIsRecordingTips(setting.isRecordingTips)
window.addEventListener('customStorageChange', handleCustomStorageChange); window.addEventListener('customStorageChange', handleCustomStorageChange);
return () => { return () => {
window.removeEventListener('customStorageChange', handleCustomStorageChange); window.removeEventListener('customStorageChange', handleCustomStorageChange);
@ -487,6 +492,7 @@ const RecordingComponents = () => {
<div> <div>
<span></span> <span></span>
<div className={styles.recordingComponents}> <div className={styles.recordingComponents}>
<div>
<span></span> <span></span>
<div> <div>
<span></span> <span></span>
@ -524,6 +530,17 @@ const RecordingComponents = () => {
}} style={{ backgroundColor: '#31353A' }}></Button> }} style={{ backgroundColor: '#31353A' }}></Button>
</div> </div>
</div> </div>
<div>
<span></span>
<div>
<Checkbox onChange={async (e) => {
setting.isRecordingTips = e.target.checked;
storage.setItem('setting', JSON.stringify(setting))
setIsRecordingTips(e.target.checked)
}} checked={isRecordingTips}></Checkbox>
</div>
</div>
</div>
</div> </div>
</> </>
) )

View File

@ -60,7 +60,7 @@ const UserVideo: React.FC = () => {
useEffect(() => { useEffect(() => {
userList.forEach(async (item: any) => { userList.forEach(async (item: any) => {
await agora.destroyRendererByConfig(Number('1' + item.screenShareId)) await agora.destroyRendererByConfig(Number('1' + item.screenShareId), state.channelId + 'a')
await agora.setupRemoteVideoEx({ await agora.setupRemoteVideoEx({
uid: Number('1' + item.screenShareId), uid: Number('1' + item.screenShareId),
view: document.getElementById(`video-${item.screenShareId}`), view: document.getElementById(`video-${item.screenShareId}`),

View File

@ -0,0 +1,133 @@
.chatBigWindow {
height: 100%;
width: 100%;
box-sizing: border-box;
display: flex;
flex-direction: column;
box-sizing: border-box;
padding: 10px 0;
.chatBigWindowTitle {
background-color: #16191E;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
padding: 10px 10px;
>span {
color: #EEEEEE;
font-size: 18px;
}
>img {
cursor: pointer;
}
}
.chatBigWindowContent {
flex-grow: 1;
height: 0px;
overflow-y: auto;
background-color: rgb(31, 33, 37);
padding: 10px;
.chatBigWindowContentLeft {
display: flex;
flex-direction: column;
align-items: flex-start;
>div:nth-child(1) {
display: flex;
align-items: center;
>span {
font-size: 14px;
color: #F3F3F5;
margin-left: 4px;
}
>div {}
}
>div:nth-child(2) {
background-color: #5575F2;
color: #F3F3F5;
max-width: 266px;
padding: 10px;
box-sizing: border-box;
border-radius: 0 25px 25px 25px;
margin: 10px 0 10px 40px;
font-size: 14px;
}
}
.chatBigWindowContentRight {
display: flex;
flex-direction: column;
align-items: flex-end;
>div:nth-child(1) {
display: flex;
align-items: center;
flex-direction: row-reverse;
>span {
font-size: 14px;
color: #F3F3F5;
}
>div {
margin-left: 4px;
}
}
>div:nth-child(2) {
background-color: #464E6B;
color: #F3F3F5;
max-width: 266px;
padding: 10px;
box-sizing: border-box;
border-radius: 25px 0 25px 25px;
margin: 10px 40px 10px 0;
font-size: 14px;
}
}
}
.chatBigWindowButton {
display: flex;
flex-shrink: 0;
flex-wrap: wrap;
padding: 10px;
box-sizing: border-box;
background-color: rgb(31, 33, 37);
>button {
margin: 4px 4px 0;
}
}
.chatBigWindowInput {
flex-shrink: 0;
padding: 10px 10px;
background-color: #16191E;
display: flex;
flex-direction: column;
align-items: flex-end;
}
}
.chatBigWindowInputPopover {
display: flex;
flex-direction: column;
>button {
margin-bottom: 4px;
&:last-child {
margin-bottom: 0;
}
}
}

View File

@ -0,0 +1,284 @@
import styles from '@/page/Meeting/ChatBigWindow/index.module.scss'
import ImageUrl from '@/utils/package/imageUrl';
import { useEffect, useState, useRef } from "react";
import { storage } from '@/utils';
import { Button, Input, Modal, Popover } from 'antd';
import { role } from '@/config/role';
import { GetRoomUserItem } from '@/api/Meeting';
import Avatar from '@/components/Avatar';
import dayjs from 'dayjs';
import { ExclamationCircleFilled } from '@ant-design/icons';
import EquipmentManagement from '@/components/EquipmentManagement';
const { confirm } = Modal;
const ChatBigWindow: React.FC = () => {
const [inputValue, setInputValue] = useState<string>('')
const [chatLists, setChatLists] = useState<any>([])
const [user, setUser] = useState<any>({});
const [roomUserItem, setRoomUserItem] = useState<any>(null)
const [commonlyChatList] = useState<any>([
'能听到我说话吗?',
'听得到',
'听不到',
'我要发言',
])
const equipmentManagementRef = useRef<any>();
const userInfo = JSON.parse(storage.getItem('user') as string)
const stateInfo = JSON.parse(storage.getItem('stateInfo') as string)
const channel = new BroadcastChannel('meeting_channel');
useEffect(() => {
setUser(userInfo)
channel.onmessage = function (event) {
const { type, showDriverList } = event.data;
switch (type) {
case 'showDriverList':
equipmentManagementRef.current.setData(showDriverList)
break;
}
}
window.electron.windowHandleMessageCallBack((_e: any, data: any) => {
setChatLists(data.parmes.chatList)
})
channel.postMessage({
type: 'chatBigWindowSendChannelMsg',
chatBigWindowSendChannelMsg: {
msg: '',
}
});
return () => {
channel.close();
}
}, []);
useEffect(() => {
const chatBigWindowView = document.getElementById('chatBigWindowView') as HTMLElement;
if (chatBigWindowView) {
chatBigWindowView.scrollTop = chatBigWindowView.scrollHeight;
}
}, [chatLists]);
return (
<>
<div className={`${styles.chatBigWindow}`}>
<div className={styles.chatBigWindowTitle}>
<span></span>
<img src={ImageUrl.icon18} className='drag' alt="" onClick={() => {
window.electron.setChildWindowShow({
key: 'chatBigWindow',
})
}} />
</div>
<div className={`${styles.chatBigWindowContent} drag`} id='chatBigWindowView'>
{chatLists.map((item: any, index: number) =>
<div
key={index}
className={`${item.uid !== user.uid ? styles.chatBigWindowContentLeft : styles.chatBigWindowContentRight}`}>
{role.ID.includes(user.roleId) ? <Popover
placement="bottom"
title={''}
onOpenChange={(e: boolean) => {
if (e) {
GetRoomUserItem(stateInfo.channelId, item.uid).then((res: any) => {
if (res.code === 200) {
setRoomUserItem(res.data)
}
})
} else {
setRoomUserItem(null)
}
}}
content={
roomUserItem ? <div className={styles.chatBigWindowInputPopover}>
{roomUserItem.isRoomManager || role.ID.includes(roomUserItem.roleId) ? <Button
type="primary"
className='m-ant-btn'
size={'small'}
onClick={(event) => {
event.stopPropagation();
channel.postMessage({
type: 'chatBigWindowSetAllUserLook',
chatBigWindowSetAllUserLook: {
roomUserItem,
}
});
}}
>Ta</Button> : null}
{roomUserItem.uid !== user.uid && !role.ID.includes(roomUserItem.roleId) ? <Button
type="primary"
className='m-ant-btn'
size={'small'}
onClick={async (event) => {
event.stopPropagation();
if (roomUserItem.isRoomManager) {
channel.postMessage({
type: 'chatBigWindowDeleteRoomManager',
chatBigWindowDeleteRoomManager: {
uid: roomUserItem.uid
}
});
} else {
channel.postMessage({
type: 'chatBigWindowPostRoomManager',
chatBigWindowPostRoomManager: {
uid: roomUserItem.uid
}
});
}
await GetRoomUserItem(stateInfo.channelId, item.uid).then((res: any) => {
if (res.code === 200) {
setRoomUserItem(res.data)
}
})
}}
>{roomUserItem.isRoomManager ? '取消发言' : '允许发言'}</Button> : null}
{roomUserItem.isRoomManager ? <Button
type="primary"
className='m-ant-btn'
size={'small'}
onClick={async (event) => {
event.stopPropagation();
channel.postMessage({
type: 'chatBigWindowPostOpenMicr',
chatBigWindowPostOpenMicr: {
uid: roomUserItem.uid,
enableMicr: !roomUserItem.enableMicr
}
});
await GetRoomUserItem(stateInfo.channelId, item.uid).then((res: any) => {
if (res.code === 200) {
setRoomUserItem(res.data)
}
})
}}
>{roomUserItem.enableMicr ? '静音' : '解除静音'}</Button> : null}
{roomUserItem.isRoomManager ? <Button
type="primary"
className='m-ant-btn'
size={'small'}
onClick={async (event) => {
event.stopPropagation();
channel.postMessage({
type: 'chatBigWindowPostOpenCamera',
chatBigWindowPostOpenCamera: {
uid: roomUserItem.uid,
enableMicr: !roomUserItem.enableCamera
}
});
await GetRoomUserItem(stateInfo.channelId, item.uid).then((res: any) => {
if (res.code === 200) {
setRoomUserItem(res.data)
}
})
}}
>{roomUserItem.enableCamera ? '关闭视频' : '打开视频'}</Button> : null}
{roomUserItem.uid !== user.uid ? <Button
type="primary"
className='m-ant-btn'
size={'small'}
onClick={() => {
equipmentManagementRef.current.changeModal(item.uid, item.userName)
}}
></Button> : null}
{roomUserItem.uid !== user.uid ? <Button
type="primary"
style={{ backgroundColor: '#EC3C3C' }}
size={'small'}
onClick={(event) => {
event.stopPropagation();
confirm({
title: '移出会议',
icon: <ExclamationCircleFilled />,
content: `确定将用户${item.userName}移出会议?`,
centered: true,
okText: '确定',
cancelText: '取消',
async onOk() {
channel.postMessage({
type: 'chatBigWindowGetRoomKickout',
chatBigWindowGetRoomKickout: {
uid: item.uid
}
});
},
onCancel() {
},
});
}}
></Button> : null}
</div> : <div style={{ color: 'white' }}></div>
}>
<div>
<div><Avatar name={item.userName} /></div>
{item.uid !== user.uid ?
<span>{item.userName} <span style={{ fontSize: '12px', color: '#ccc', marginLeft: '4px' }}>{dayjs(item.timestamp).format('HH:mm:ss')}</span></span> :
<span> <span style={{ fontSize: '12px', color: '#ccc', marginRight: '4px' }}>{dayjs(item.timestamp).format('HH:mm:ss')} </span>{item.userName}</span>
}
</div>
</Popover> : <div>
<div><Avatar name={item.userName} /></div>
{item.uid !== user.uid ?
<span>{item.userName}<span style={{ fontSize: '12px', color: '#ccc', marginLeft: '4px' }}>{dayjs(item.timestamp).format('HH:mm:ss')}</span></span> :
<span><span style={{ fontSize: '12px', color: '#ccc', marginRight: '4px' }}>{dayjs(item.timestamp).format('HH:mm:ss')} </span>{item.userName}</span>
}
</div>}
<div>{item.message}</div>
</div>
)}
</div>
<div className={`${styles.chatBigWindowButton} drag`}>
{
commonlyChatList.map((item: string, index: number) => {
return <Button
key={index}
type="primary"
className='m-ant-btn'
onClick={() => {
channel.postMessage({
type: 'chatBigWindowSendChannelMsg',
chatBigWindowSendChannelMsg: {
msg: item,
}
});
}}
>{item}</Button>
})
}
</div>
<div className={`${styles.chatBigWindowInput} drag`}>
<Input.TextArea
placeholder="请输入内容"
style={{ flexGrow: 1 }}
value={inputValue}
onChange={(e) => {
setInputValue(e.target.value)
}}
autoSize={{ minRows: 3, maxRows: 3 }} />
<Button type="primary" className='m-ant-btn' style={{ flexShrink: 0, marginTop: '4px' }} onClick={() => {
channel.postMessage({
type: 'chatBigWindowSendChannelMsg',
chatBigWindowSendChannelMsg: {
msg: inputValue,
}
});
setInputValue('')
}}></Button>
</div>
</div>
<EquipmentManagement ref={equipmentManagementRef} getDriver={(uid: string) => {
channel.postMessage({
type: 'userListWindowEquipmentManagement',
userListWindowEquipmentManagement: {
uid
}
});
}} setDriver={(data: any) => {
channel.postMessage({
type: 'userListWindowSetEquipmentManagement',
userListWindowSetEquipmentManagement: data
});
}} />
</>
)
}
export default ChatBigWindow

View File

@ -0,0 +1,96 @@
.chatSmallWindow {
color: white;
width: 100%;
display: flex;
flex-direction: column;
height: 100%;
position: relative;
>div:nth-child(1) {
flex-grow: 1;
overflow-y: hidden;
max-height: 80%;
padding: 0 4px;
box-sizing: border-box;
display: flex;
background-color: rgba(40, 40, 44, .4);
flex-direction: column-reverse;
.chatSmallWindowContentLeft {
display: flex;
flex-direction: column;
align-items: flex-start;
>div {
background-color: #1e232baf;
padding: 4px;
box-sizing: border-box;
border-radius: 0 15px 15px 15px;
margin: 0 0 4px 0;
font-size: 12px;
display: flex;
>span:nth-child(1) {
white-space: nowrap;
color: #ff970f;
}
>span:nth-child(2) {
color: #fff;
}
}
}
.chatSmallWindowContentRight {
display: flex;
flex-direction: column;
align-items: flex-end;
>div {
background-color: #464e6b55;
color: black;
padding: 4px;
box-sizing: border-box;
border-radius: 15px 0 15px 15px;
margin: 0 0 4px 0;
font-size: 14px;
display: flex;
flex-direction: row-reverse;
>span:nth-child(1) {
white-space: nowrap;
}
}
}
}
>div:nth-child(2) {
flex-shrink: 0;
opacity: 0.4;
&:hover {
opacity: 1;
}
}
>div:nth-child(3) {
position: absolute;
left: 50%;
top: 0px;
transform: translate(-50%, 0);
display: flex;
align-items: center;
margin: 0 auto;
background-color: rgba(40, 40, 44, .2);
padding: 0 6px;
cursor: pointer;
&:hover {
background-color: rgba(40, 40, 44, 1);
}
>span {
font-size: 12px;
}
}
}

View File

@ -0,0 +1,101 @@
import styles from '@/page/Meeting/ChatSmallWindow/index.module.scss'
import { CaretDownOutlined, CaretUpOutlined } from '@ant-design/icons';
import { Input } from 'antd';
import { useEffect, useState } from "react";
const ChatSmallWindow: React.FC = () => {
const [inputValue, setInputValue] = useState<string>('')
const [chatLists, setChatLists] = useState<any>([])
const [isExpand, setIsExpand] = useState(false)
const channel = new BroadcastChannel('meeting_channel');
useEffect(() => {
window.electron.windowHandleMessageCallBack((_e: any, data: any) => {
setChatLists((newChatList: any) => {
data.parmes.chatListIten.timer = setTimeout(() => {
removeItemByIndex();
}, 10000);
return [data.parmes.chatListIten, ...newChatList]
})
})
return () => {
channel.close();
}
}, []);
useEffect(() => {
const chatSmallWindowView = document.getElementById('chatSmallWindowView') as HTMLElement;
if (chatSmallWindowView) {
chatSmallWindowView.scrollTop = chatSmallWindowView.scrollHeight;
}
}, [chatLists]);
const removeItemByIndex = () => {
setChatLists((res: any) => {
return res.slice(0, -1)
})
}
return (
<>
<div className={styles.chatSmallWindow}>
<div id='chatSmallWindowView'>
{chatLists.map((item: any, index: number) =>
<div className={`${styles.chatSmallWindowContentLeft}`} key={index}>
<div>
<span>{item.userName}</span>
<span>{item.message}</span>
</div>
</div>
)}
</div>
<div className='drag'>
<Input
placeholder="请输入内容"
style={{ flexGrow: 1 }}
value={inputValue}
onChange={(e) => {
setInputValue(e.target.value)
}}
onPressEnter={() => {
if (inputValue) {
channel.postMessage({
type: 'chatSmallWindowSendChannelMsg',
chatSmallWindowSendChannelMsg: {
msg: inputValue,
}
});
setInputValue('')
}
}}
suffix={
<span
style={{ color: '#47D3D0', cursor: 'pointer' }}
onClick={() => {
if (inputValue) {
channel.postMessage({
type: 'chatSmallWindowSendChannelMsg',
chatSmallWindowSendChannelMsg: {
msg: inputValue,
}
});
setInputValue('')
}
}}
>
</span>
}
/>
</div>
<div className={`drag`} onClick={() => {
setIsExpand(!isExpand)
window.electron.setChildWindow({
height: isExpand ? 150 : 150 / 2,
key: 'chatSmallWindow',
})
}}>
<span>{isExpand ? '展开' : '收起'}</span>
{isExpand ? <CaretDownOutlined /> : <CaretUpOutlined />}
</div>
</div >
</>
)
}
export default ChatSmallWindow

View File

@ -0,0 +1,19 @@
.currentSpeakUserWindow {
color: white;
width: 100%;
display: flex;
flex-direction: column;
height: 100%;
background-color: #16191E;
padding: 4px;
box-sizing: border-box;
>div {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
max-width: 100%;
width: fit-content;
font-size: 14px;
}
}

View File

@ -0,0 +1,36 @@
import styles from '@/page/Meeting/CurrentSpeakUserWindow/index.module.scss'
import { useEffect, useState } from "react";
const CurrentSpeakUserWindow: React.FC = () => {
const [inputValue, setInputValue] = useState('')
const channel = new BroadcastChannel('meeting_channel');
useEffect(() => {
let time: NodeJS.Timeout;
time = setInterval(() => {
channel.postMessage({
type: 'currentSpeakUserWindowGetUserName'
})
}, 1000)
window.electron.windowHandleMessageCallBack((_e: any, data: any) => {
if (data.parmes.currentSpeakUser.length) {
setInputValue(data.parmes.currentSpeakUser.join(';'))
} else {
setInputValue('')
}
})
return () => {
clearInterval(time)
}
}, []);
return (
<>
<div className={styles.currentSpeakUserWindow}>
<div title={`正在说话: ${inputValue}`}>
{`正在说话: ${inputValue}`}
</div>
</div >
</>
)
}
export default CurrentSpeakUserWindow

View File

@ -0,0 +1,6 @@
.noticeWindow {
height: 100%;
width: 100%;
box-sizing: border-box;
background-color: transparent;
}

View File

@ -0,0 +1,100 @@
import styles from '@/page/Meeting/NoticeWindow/index.module.scss'
import { Button, notification } from 'antd';
import { useEffect } from "react";
const NoticeWindow: React.FC = () => {
const [api, contextHolder] = notification.useNotification({
stack: {
threshold: 3
}
});
const channel = new BroadcastChannel('meeting_channel');
useEffect(() => {
window.electron.setChildWindowShow({
key: 'noticeWindow',
bool: false
})
channel.onmessage = function (event) {
const { type, noticeItem } = event.data;
switch (type) {
case 'noticeItem':
api.open({
message: '',
description: <div>
<span style={{ fontSize: '16px' }}>{noticeItem.uname}</span>
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
<Button
type="primary"
className='m-ant-btn'
onClick={async (e: any) => {
let i = e.nativeEvent.path;
if (i) {
i.forEach((i: any) => {
if (i.className === 'ant-notification-notice ant-notification-notice-closable') {
i.childNodes.forEach((row: any) => {
if (row.className === 'ant-notification-notice-close') {
row.click()
channel.postMessage({
type: 'noticeWindowPostRoomManager',
noticeWindowPostRoomManager: {
uid: noticeItem.uid
}
});
}
})
}
})
}
}}
></Button>
<Button
type="primary"
onClick={(e: any) => {
let item = e.nativeEvent.path;
if (item) {
item.forEach((item: any) => {
if (item.className === 'ant-notification-notice ant-notification-notice-closable') {
item.childNodes.forEach((row: any) => {
if (row.className === 'ant-notification-notice-close') {
row.click()
}
})
}
})
}
}}
style={{ backgroundColor: '#EC3C3C', marginLeft: '14px' }}
></Button>
</div>
</div>,
onClose: () => {
const dom = document.getElementsByClassName('ant-notification')
if (dom.length === 0) {
window.electron.setChildWindowShow({
key: 'noticeWindow',
bool: false
})
}
},
duration: 10,
placement: 'bottomRight',
showProgress: true,
pauseOnHover: false,
});
break;
}
}
return () => {
channel.close();
};
}, []);
return (
<>
<div className={`${styles.noticeWindow} drag`}>
{contextHolder}
</div>
</>
)
}
export default NoticeWindow

View File

@ -0,0 +1,85 @@
.shareScreenWindow {
color: white;
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
.shareScreenWindowTitle {
font-size: 12px;
flex-shrink: 0;
padding: 4px;
box-sizing: border-box;
background-color: lighten(#07090B, 4%);
display: flex;
justify-content: space-between;
align-items: center;
>span:nth-child(2) {
cursor: pointer;
background-color: #FF5219;
padding: 1px 10px;
border-radius: 2px;
font-size: 12px;
}
}
.shareScreenWindowContent {
flex-grow: 1;
align-items: center;
justify-content: space-between;
background-color: #07090B;
display: flex;
.shareScreenWindowContentList {
display: flex;
flex-grow: 1;
justify-content: space-between;
>div {
display: flex;
flex-direction: column;
align-items: center;
cursor: pointer;
width: calc(100% / 6);
>div {
position: relative;
>img {
height: 24px;
width: 24px;
}
>div {
position: absolute;
left: 0;
bottom: 0;
background-repeat: no-repeat;
background-position: bottom center;
background-size: cover;
width: 100%;
height: 0%;
}
}
>span {
font-size: 10px;
}
}
}
}
.shareScreenWindowExpand {
display: flex;
align-items: center;
margin: 0 auto;
background-color: lighten(#07090B, 4%);
padding: 0 6px;
cursor: pointer;
>span {
font-size: 12px;
}
}
}

View File

@ -0,0 +1,212 @@
import { GetRoomUser } from '@/api/Meeting';
import { role } from '@/config/role';
import styles from '@/page/Meeting/ShareScreenWindow/index.module.scss'
import { storage } from '@/utils';
import ImageUrl from '@/utils/package/imageUrl';
import { CaretDownOutlined, CaretUpOutlined } from '@ant-design/icons';
import { Button } from 'antd';
import dayjs from 'dayjs';
import { useEffect, useState } from "react";
const ShareScreenWindow: React.FC = () => {
const [footerLists, setFooterLists] = useState<any>([
{
title: '静音',
icon: ImageUrl.icon22,
iconActive: ImageUrl.icon22Active,
active: false,
select: false,
},
{
title: '关闭视频',
icon: ImageUrl.icon23,
iconActive: ImageUrl.icon23Active,
active: false,
select: false,
},
{
title: '成员',
icon: ImageUrl.icon30,
iconSelect: ImageUrl.icon30Select,
active: false,
select: false,
},
{
title: '聊天',
icon: ImageUrl.icon31,
iconSelect: ImageUrl.icon31Select,
active: false,
select: false,
},
{
title: '录制',
icon: ImageUrl.icon27,
iconSelect: ImageUrl.icon27Select,
iconActive: ImageUrl.icon27Active,
active: false,
select: false,
},
])
const [timeStr, setTimeStr] = useState(0)
const [isExpand, setIsExpand] = useState(false)
const [roomUserLists, setRoomUserLists] = useState<any>([])
const channel = new BroadcastChannel('meeting_channel');
const userInfo = JSON.parse(storage.getItem('user') as string)
let timeout: NodeJS.Timeout;
useEffect(() => {
getRoomUser()
channel.onmessage = function (event) {
let { type, time } = event.data;
switch (type) {
case 'time':
setTimeStr(time)
timeout = setInterval(() => {
setTimeStr(time++)
}, 1000)
break;
}
}
channel.postMessage({
type: 'shareScreenWindowGetTime'
});
window.electron.windowHandleMessageCallBack((_e: any, data: any) => {
switch (data.parmes.type) {
case 'currentSpeakUserMe':
let domMe = document.getElementById(`micr-item-${userInfo.uid}`) as HTMLDivElement;
if (domMe) {
domMe.style.height = `${data.parmes.currentSpeakUserMe}%`
}
break;
case 'footerList':
const footerListTemplate = [...footerLists];
footerListTemplate[0].title = data.parmes.footerList[0][0].active ? '解除静音' : '静音';
footerListTemplate[0].active = data.parmes.footerList[0][0].active;
footerListTemplate[1].title = data.parmes.footerList[0][1].active ? '开启视频' : '关闭视频';
footerListTemplate[1].active = data.parmes.footerList[0][1].active;
footerListTemplate[4].title = data.parmes.footerList[1][3].active ? '录制中' : '录制';
footerListTemplate[4].active = data.parmes.footerList[1][3].active;
setFooterLists(footerListTemplate)
break;
case 'roomUserList':
setRoomUserLists(data.parmes.roomUserList)
break;
}
})
return () => {
clearInterval(timeout)
channel.close();
};
}, []);
const changeCurrentSeconds = (time: number): string => {
const duration = dayjs.duration(time, '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 getRoomUser = async (): Promise<void> => {
const data = JSON.parse(storage.getItem('stateInfo') as string)
await GetRoomUser(data.channelId).then(res => {
if (res.code === 200) {
res.data.forEach((item: any) => {
item.isShow = true;
item.isRoom = true;
item.isAdmin = role.ID.includes(item.roleId) || item.isRoomManager
})
setRoomUserLists(res.data)
channel.postMessage({
type: 'shareScreenWindowGetFooterLists',
});
}
})
}
// 底部按钮点击效果
const changeFooterListSelect = (item: any, index: number, bool: boolean): void => {
let arr = ['静音', '解除静音', '关闭视频', '开启视频']
if (arr.indexOf(item.title) === -1) {
const footerListTemplate = [...footerLists]
footerListTemplate[index].select = bool;
setFooterLists(footerListTemplate)
}
}
return (
<>
<div className={styles.shareScreenWindow} style={{ width: isExpand ? '100%' : '100%' }}>
<div className={styles.shareScreenWindowTitle}>
<span>{changeCurrentSeconds(timeStr)} </span>
{isExpand ? <span className='drag' onClick={() => {
channel.postMessage({
type: 'shareScreenWindowClose',
shareScreenWindowClose: timeStr
});
}}></span> : <span style={{ visibility: 'hidden' }}></span>}
</div>
{isExpand ? null : <div className={`${styles.shareScreenWindowContent} drag`}>
<div className={styles.shareScreenWindowContentList}>
{footerLists.map((item: any, index: number) => {
return (
<div
onMouseDown={() => changeFooterListSelect(item, index, true)}
onMouseUp={() => changeFooterListSelect(item, index, false)}
onMouseLeave={() => changeFooterListSelect(item, index, false)}
onClick={async () => {
switch (item.title) {
case '静音':
case '解除静音':
case '关闭视频':
case '开启视频':
case '录制':
case '录制中':
channel.postMessage({
type: 'shareScreenWindowfooterListsTitle',
shareScreenWindowfooterListsTitle: item.title
});
break;
case '聊天':
window.electron.setChildWindowShow({
key: 'chatBigWindow',
})
break;
case '成员':
window.electron.setChildWindowShow({
key: 'userListWindow',
})
break;
}
}}
key={index}>
<div>
{!item.active ? <div style={{ backgroundImage: `url(${ImageUrl.icon49})` }} id={`micr-item-${userInfo.uid}`}>
</div> : ''}
{item.select ? <img src={item.iconSelect} alt="" /> : <img src={item.active ? item.iconActive : item.icon} alt="" />}
</div>
<span>{item.title}{item.title === '成员' ? `(${roomUserLists.filter((item: any) => item.isRoom).length})` : ''}</span>
</div>
)
})}
</div>
<Button type="primary" style={{ backgroundColor: '#FF5219', marginRight: '14px' }} size={'small'} onClick={() => {
channel.postMessage({
type: 'shareScreenWindowClose',
shareScreenWindowClose: timeStr
});
}}>
</Button>
</div>}
<div className={`${styles.shareScreenWindowExpand} drag`} onClick={() => {
setIsExpand(!isExpand)
window.electron.setChildWindow({
width: isExpand ? 440 : 440 / 2,
key: 'shareScreenWindow',
})
}}>
<span>{isExpand ? '展开' : '收起'}</span>
{isExpand ? <CaretDownOutlined /> : <CaretUpOutlined />}
</div>
</div >
</>
)
}
export default ShareScreenWindow

View File

@ -0,0 +1,120 @@
.userListWindow {
height: 100%;
width: 100%;
box-sizing: border-box;
background-color: #16191E;
display: flex;
flex-direction: column;
box-sizing: border-box;
padding: 10px 0;
>div {
margin-bottom: 10px;
&:last-child {
margin-bottom: 0px;
}
}
.userListWindowTitle {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
padding: 0 10px;
>span {
color: #EEEEEE;
font-size: 18px;
}
>img {
cursor: pointer;
}
}
.userListWindowContent {
flex-grow: 1;
height: 0px;
overflow-y: auto;
>div {
display: flex;
align-items: center;
justify-content: space-between;
position: relative;
box-sizing: border-box;
padding: 4px 10px;
>div:nth-child(1) {
display: flex;
align-items: center;
>span {
font-size: 14px;
color: #F3F3F5;
margin-left: 4px;
}
>div {
flex-shrink: 0;
}
}
>div:nth-child(2) {
display: flex;
align-items: center;
>div {
height: 30px;
width: 30px;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
>img {
width: 20px;
}
&:hover {
background-color: rgba(0, 0, 0, 0.5);
}
}
}
&:hover {
background-color: rgb(52, 52, 52);
}
}
}
.userListWindowFooter {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
>div {
font-size: 14px;
cursor: pointer;
background-color: #31353A;
color: #EEEEEE;
width: 104px;
height: 30px;
line-height: 30px;
text-align: center;
border-radius: 5px;
&:hover {
background-color: lighten(#31353A, 4%);
}
&:active {
background-color: darken(#31353A, 4%);
}
}
}
}

View File

@ -0,0 +1,212 @@
import { role } from '@/config/role';
import styles from '@/page/Meeting/UserListWindow/index.module.scss'
import ImageUrl from '@/utils/package/imageUrl';
import { EllipsisOutlined, ExclamationCircleFilled, SearchOutlined } from '@ant-design/icons';
import { Button, Input, Modal, Popover } from 'antd';
import Avatar from '@/components/Avatar';
import { useEffect, useState, useRef } from "react";
import { storage } from '@/utils';
import EquipmentManagement from '@/components/EquipmentManagement';
const { confirm } = Modal;
const UserListWindow: React.FC = () => {
const [userSearchValue, setUserSearchValue] = useState('')
const [user, setUser] = useState<any>({});
const [roomUserList, setRoomUserList] = useState<any>([])
const equipmentManagementRef = useRef<any>();
const channel = new BroadcastChannel('meeting_channel');
const userInfo = JSON.parse(storage.getItem('user') as string)
useEffect(() => {
setUser(userInfo)
channel.onmessage = function (event) {
const { type, showDriverList } = event.data;
switch (type) {
case 'showDriverList':
equipmentManagementRef.current.setData(showDriverList)
break;
}
}
channel.postMessage({
type: 'userListWindowGetRoomUserList'
});
window.electron.windowHandleMessageCallBack((_e: any, data: any) => {
switch (data.parmes.type) {
case 'roomUserList':
setRoomUserList(data.parmes.roomUserList)
break;
}
})
return () => {
channel.close();
}
}, []);
return (
<>
<div className={`${styles.userListWindow}`}>
<div className={styles.userListWindowTitle}>
<span></span>
<img src={ImageUrl.icon18} className='drag' alt="" onClick={() => {
window.electron.setChildWindowShow({
key: 'userListWindow',
})
}} />
</div>
<div className='drag' style={{ padding: '0 10px' }}>
<Input
placeholder="请输入用户名"
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.userListWindowContent} drag`}>
{roomUserList.map((item: any, index: number) => {
return (
item.isShow && item.isRoom ? <div key={index + item.uid}>
<div>
<div><Avatar name={item.userName} /></div>
<span>
{item.userName}{item.uid === user.uid ? '(我)' : ''}
{role.ID.includes(item.roleId) || item.isRoomManager ?
<span style={{ color: '#02B188', marginLeft: '4px' }}>
{role.ID.includes(item.roleId) ? '管理员' : '发言人'}
</span>
: null}
</span>
</div>
<div>
{role.ID.includes(item.roleId) || item.isRoomManager ? <div>
<img src={item.enableMicr ? ImageUrl.icon22 : ImageUrl.icon22Active} alt="" onClick={() => {
channel.postMessage({
type: 'userListWindowPostOpenMicr',
userListWindowPostOpenMicr: {
enableMicr: !item.enableMicr,
uid: item.uid
}
});
}} title={item.enableMicr ? '静音' : '解除静音'} />
</div> : null}
{role.ID.includes(item.roleId) || item.isRoomManager ? <div>
<img src={item.enableCamera ? ImageUrl.icon23 : ImageUrl.icon23Active} alt="" onClick={() => {
channel.postMessage({
type: 'userListWindowPostOpenCamera',
userListWindowPostOpenCamera: {
enableCamera: !item.enableCamera,
uid: item.uid
}
});
}} title={item.enableCamera ? '关闭视频' : '开启视频'} />
</div> : null}
{item.uid !== user.uid && role.ID.includes(user.roleId) ? <div>
<Popover placement="left" title={''} content={
<div style={{ width: '100px' }}>
<Button
type="primary"
className='m-ant-btn'
style={{ width: '100%' }}
size={'small'}
onClick={() => {
equipmentManagementRef.current.changeModal(item.uid, item.userName)
}}
></Button>
<Button
type="primary"
style={{ backgroundColor: '#EC3C3C', width: '100%', marginTop: '10px' }}
size={'small'}
onClick={() => {
confirm({
title: '移出会议',
icon: <ExclamationCircleFilled />,
content: `确定将用户${item.userName}移出会议?`,
centered: true,
okText: '确定',
cancelText: '取消',
async onOk() {
channel.postMessage({
type: 'userListWindowGetRoomKickout',
userListWindowGetRoomKickout: {
uid: item.uid
}
});
},
onCancel() {
},
});
}}
></Button>
</div>
}>
<EllipsisOutlined style={{
color: '#fff',
fontSize: '20px'
}} />
</Popover>
</div> : null}
{item.uid !== user.uid && !role.ID.includes(item.roleId) && role.ID.includes(user.roleId) ? <div onClick={() => {
if (item.isRoomManager) {
channel.postMessage({
type: 'userListWindowDeleteRoomManager',
userListWindowDeleteRoomManager: {
uid: item.uid
}
});
} else {
channel.postMessage({
type: 'userListWindowPostRoomManager',
userListWindowPostRoomManager: {
uid: item.uid
}
});
}
}}>
{!item.isRoomManager ?
<img src={ImageUrl.icon50} alt="" title='允许发言' /> :
<img src={ImageUrl.icon51} alt="" title='取消发言' />}
</div> : null}
</div>
</div> : null
)
}
)}
</div>
<div className={`${styles.userListWindowFooter}`}>
<div className='drag' onClick={() => {
channel.postMessage({
type: 'userListWindowAllPostOpenMicr'
});
}}></div>
</div>
</div>
<EquipmentManagement ref={equipmentManagementRef} getDriver={(uid: string) => {
channel.postMessage({
type: 'userListWindowEquipmentManagement',
userListWindowEquipmentManagement: {
uid
}
});
}} setDriver={(data: any) => {
channel.postMessage({
type: 'userListWindowSetEquipmentManagement',
userListWindowSetEquipmentManagement: data
});
}} />
</>
)
}
export default UserListWindow

View File

@ -937,6 +937,7 @@
width: 144px; width: 144px;
height: 94px; height: 94px;
box-sizing: border-box; box-sizing: border-box;
border: 5px solid transparent;
>img { >img {
width: 100%; width: 100%;
@ -953,14 +954,16 @@
&:hover { &:hover {
>div { >div {
border: 1px solid #EBEBEB; border: 5px solid yellow;
box-sizing: border-box;
} }
} }
} }
.active { .active {
>div { >div {
border: 1px solid #EBEBEB; border: 5px solid yellow;
box-sizing: border-box;
} }
} }
} }

View File

@ -14,7 +14,7 @@ import { agora } from '@/utils/package/agora'
import { onInvoke, onSignalr, offSignalr } from '@/utils/package/signalr'; import { onInvoke, onSignalr, offSignalr } from '@/utils/package/signalr';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import durationPlugin from 'dayjs/plugin/duration'; import durationPlugin from 'dayjs/plugin/duration';
import { AudioVolumeInfo, ConnectionChangedReasonType, ConnectionStateType, QualityType, RtcConnection, RtcStats, UserOfflineReasonType, VideoSourceType, VideoStreamType } from 'agora-electron-sdk'; import { AudioVolumeInfo, ConnectionChangedReasonType, ConnectionStateType, LocalVideoStreamReason, LocalVideoStreamState, QualityType, RtcConnection, RtcStats, UserOfflineReasonType, VideoSourceType, VideoStreamType } from 'agora-electron-sdk';
import Avatar from '@/components/Avatar'; import Avatar from '@/components/Avatar';
import SharedFilesModel from '@/components/SharedFilesModel'; import SharedFilesModel from '@/components/SharedFilesModel';
import StupWizard from '@/components/StupWizard'; import StupWizard from '@/components/StupWizard';
@ -22,6 +22,7 @@ import EquipmentManagement from '@/components/EquipmentManagement';
import UserVideo from '@/components/UserVideo'; import UserVideo from '@/components/UserVideo';
import { role } from '@/config/role'; import { role } from '@/config/role';
import { fixWebmDuration } from "webm-duration-fix-buffer"; import { fixWebmDuration } from "webm-duration-fix-buffer";
import { getKeyOpenChildWindow, setKeyOpenChildWindow } from '@/utils/package/public';
const { confirm } = Modal; const { confirm } = Modal;
const { exec } = require('child_process'); const { exec } = require('child_process');
const fs = require('fs').promises; const fs = require('fs').promises;
@ -41,6 +42,7 @@ const Meeting: React.FC = () => {
userVideo: false, userVideo: false,
}) })
const [isSharedScreenModal, setIsSharedScreenModal] = useState(false); const [isSharedScreenModal, setIsSharedScreenModal] = useState(false);
const [quitMeetingModal, setQuitMeetingModal] = useState(false);
const [user, setUser] = useState<any>({}); const [user, setUser] = useState<any>({});
const [sharedScreenList, setSharedScreenList] = useState<any>([]); const [sharedScreenList, setSharedScreenList] = useState<any>([]);
const [sharedScreenItem, setSharedScreenItem] = useState<any>(''); const [sharedScreenItem, setSharedScreenItem] = useState<any>('');
@ -48,17 +50,17 @@ const Meeting: React.FC = () => {
const [footerList, setFooterList] = useState<any>([ const [footerList, setFooterList] = useState<any>([
[ [
{ {
title: '静音', title: '解除静音',
icon: ImageUrl.icon22, icon: ImageUrl.icon22,
iconActive: ImageUrl.icon22Active, iconActive: ImageUrl.icon22Active,
active: false, active: true,
select: false, select: false,
}, },
{ {
title: '关闭视频', title: '开启视频',
icon: ImageUrl.icon23, icon: ImageUrl.icon23,
iconActive: ImageUrl.icon23Active, iconActive: ImageUrl.icon23Active,
active: false, active: true,
select: false, select: false,
}, },
{ {
@ -142,6 +144,7 @@ const Meeting: React.FC = () => {
rowIndex: 0, rowIndex: 0,
}); });
const [roomUserList, setRoomUserList] = useState<any>([]) const [roomUserList, setRoomUserList] = useState<any>([])
const [_speackUid, setSpeackUid] = useState<any>([])
const [chatList, setChatList] = useState<any>([]) const [chatList, setChatList] = useState<any>([])
const [currentVideoId, setCurrentVideoId] = useState('') const [currentVideoId, setCurrentVideoId] = useState('')
let [currentSeconds, setCurrentSeconds] = useState(0) let [currentSeconds, setCurrentSeconds] = useState(0)
@ -151,7 +154,8 @@ const Meeting: React.FC = () => {
text: '网络质量极好。' text: '网络质量极好。'
}) })
const [networkOther, setNetworkOther] = useState<RtcStats>({}) const [networkOther, setNetworkOther] = useState<RtcStats>({})
const [isComputerAudio, setIsComputerAudio] = useState(true) const [isComputerAudio, setIsComputerAudio] = useState(false)
const [_isScreenCapture, setIsScreenCapture] = useState(false)
const [isFluencyPriority, setIsFluencyPriority] = useState(false) const [isFluencyPriority, setIsFluencyPriority] = useState(false)
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const [modeOpen, setModeOpen] = useState(false) const [modeOpen, setModeOpen] = useState(false)
@ -184,46 +188,265 @@ const Meeting: React.FC = () => {
const [observer, setObserver] = useState<IntersectionObserver>() const [observer, setObserver] = useState<IntersectionObserver>()
let userInfo = JSON.parse(storage.getItem('user') as string) let userInfo = JSON.parse(storage.getItem('user') as string)
const msgTips = '您不是管理员或发言人,无法开启此功能!' const msgTips = '您不是管理员或发言人,无法开启此功能!'
const channel = new BroadcastChannel('meeting_channel');
useEffect(() => { useEffect(() => {
let time: NodeJS.Timeout; let time: NodeJS.Timeout;
// let getDesktopCapturerVideoTime: NodeJS.Timeout;
setUser(userInfo) setUser(userInfo)
window.electron.getIsMaximized().then((res: boolean) => { window.electron.getIsMaximized().then((res: boolean) => {
if (!res) { if (!res) {
window.electron.setViewStatus('maximize') window.electron.setViewStatus('maximize')
} }
}) })
setKeyOpenChildWindow('shareScreenWindow', false)
setMeetingMode('StandardMode'); setMeetingMode('StandardMode');
agoraInit() agoraInit()
storage.setItem('noViewChatList', 0) storage.setItem('noViewChatList', 0)
window.addEventListener('customStorageChange', handleCustomStorageChange); window.addEventListener('customStorageChange', handleCustomStorageChange);
const container = document.getElementById('videoView') as HTMLElement; const container = document.getElementById('videoView') as HTMLElement;
container.addEventListener('wheel', handleWheelChange); container.addEventListener('wheel', handleWheelChange);
channel.onmessage = async function (event) {
const {
type,
shareScreenWindowfooterListsTitle,
shareScreenWindowClose,
userListWindowPostOpenMicr,
userListWindowPostOpenCamera,
userListWindowDeleteRoomManager,
userListWindowPostRoomManager,
userListWindowGetRoomKickout,
userListWindowEquipmentManagement,
userListWindowSetEquipmentManagement,
chatSmallWindowSendChannelMsg,
chatBigWindowSetAllUserLook,
chatBigWindowDeleteRoomManager,
chatBigWindowPostRoomManager,
chatBigWindowPostOpenMicr,
chatBigWindowPostOpenCamera,
chatBigWindowGetRoomKickout,
chatBigWindowSendChannelMsg,
noticeWindowPostRoomManager
} = event.data;
switch (type) {
case 'shareScreenWindowGetTime':
setCurrentSeconds((res => {
channel.postMessage({
type: 'time',
time: res,
});
return res
}))
break;
case 'shareScreenWindowClose':
setCurrentSeconds(shareScreenWindowClose)
await stopScreenCapture()
await allUserLook(userInfo.uid, userInfo.userName)
break;
case 'shareScreenWindowfooterListsTitle':
switch (shareScreenWindowfooterListsTitle) {
case '静音':
case '解除静音':
changeStatusList({
title: shareScreenWindowfooterListsTitle
}, 0, 1)
break;
case '关闭视频':
case '开启视频':
changeStatusList({
title: shareScreenWindowfooterListsTitle
}, 0, 2)
break;
case '录制':
case '录制中':
changeStatusList({
title: shareScreenWindowfooterListsTitle
}, 1, 3)
break;
}
break;
case 'shareScreenWindowGetFooterLists':
setFooterList((res: any) => {
window.electron.windowHandleMessage({
key: 'shareScreenWindow',
parmes: {
footerList: res,
type: 'footerList'
}
})
return res
})
break;
case 'userListWindowEquipmentManagement':
await onInvoke('getDrivers', {
uid: userListWindowEquipmentManagement.uid,
})
break;
case 'userListWindowSetEquipmentManagement':
await onInvoke('setDrivers', {
uid: userListWindowSetEquipmentManagement.uid,
driversJsonString: userListWindowSetEquipmentManagement.driversJsonString
})
break;
case 'userListWindowPostOpenMicr':
postOpenMicr(userListWindowPostOpenMicr.enableMicr, userListWindowPostOpenMicr.uid)
break;
case 'userListWindowPostOpenCamera':
postOpenCamera(userListWindowPostOpenCamera.enableCamera, userListWindowPostOpenCamera.uid)
break;
case 'userListWindowDeleteRoomManager':
DeleteRoomManager({
roomId: state.roomId,
roomNum: state.channelId,
userId: userListWindowDeleteRoomManager.uid
})
break;
case 'userListWindowPostRoomManager':
postRoomManager({
roomId: state.roomId,
roomNum: state.channelId,
userId: userListWindowPostRoomManager.uid
})
break;
case 'userListWindowGetRoomUserList':
setRoomUserList(((res: any) => {
window.electron.windowHandleMessage({
key: 'userListWindow',
parmes: {
roomUserList: res,
type: 'roomUserList'
}
})
return res
}))
break;
break;
case 'userListWindowGetRoomKickout':
GetRoomKickout(state.channelId, userListWindowGetRoomKickout.uid)
break;
case 'userListWindowAllPostOpenMicr':
postOpenMicr(false, userInfo.id, true)
break;
case 'chatSmallWindowSendChannelMsg':
sendMsg(chatSmallWindowSendChannelMsg.msg)
break;
case 'chatBigWindowSetAllUserLook':
setAllUserLook(chatBigWindowSetAllUserLook.roomUserItem)
break;
case 'chatBigWindowDeleteRoomManager':
DeleteRoomManager({
roomId: state.roomId,
roomNum: state.channelId,
userId: chatBigWindowDeleteRoomManager.uid
})
break;
case 'chatBigWindowPostRoomManager':
postRoomManager({
roomId: state.roomId,
roomNum: state.channelId,
userId: chatBigWindowPostRoomManager.uid
})
break;
case 'chatBigWindowPostOpenMicr':
postOpenMicr(chatBigWindowPostOpenMicr.enableMicr, chatBigWindowPostOpenMicr.uid)
break;
case 'chatBigWindowPostOpenCamera':
postOpenCamera(chatBigWindowPostOpenCamera.enableCamera, chatBigWindowPostOpenCamera.uid)
break;
case 'chatBigWindowGetRoomKickout':
GetRoomKickout(state.channelId, chatBigWindowGetRoomKickout.uid)
break;
case 'chatBigWindowSendChannelMsg':
if (chatBigWindowSendChannelMsg.msg) {
sendMsg(chatBigWindowSendChannelMsg.msg)
} else[
setChatList((res: any) => {
window.electron.windowHandleMessage({
key: 'chatBigWindow',
parmes: {
chatList: res,
}
})
return res
})
]
break;
case 'noticeWindowPostRoomManager':
postRoomManager({
roomId: state.roomId,
roomNum: state.channelId,
userId: noticeWindowPostRoomManager.uid
})
break;
case 'currentSpeakUserWindowGetUserName':
setSpeackUid((uids: any) => {
const usernames: string[] = [];
setRoomUserList((res: any) => {
uids.forEach((uid: any) => {
const user = res.find((item: any) => item.uid == uid);
if (user) {
usernames.push(user.userName);
}
})
window.electron.windowHandleMessage({
key: 'currentSpeakUserWindow',
parmes: {
currentSpeakUser: usernames,
}
})
return res
});
return []
})
break;
}
}
time = setInterval(() => { time = setInterval(() => {
setCurrentSeconds(currentSeconds++) setCurrentSeconds(currentSeconds => {
return currentSeconds += 1
})
}, 1000) }, 1000)
// getDesktopCapturerVideoTime = setInterval(() => { // 首次加载图标更新
// setSharedScreenItem((i: any) => { const firstFooterList = [...footerList]
// if (i && i.type === 0) { firstFooterList[0][0].title = state.enableMicr ? '静音' : '解除静音'
// agora.getDesktopCapturerVideo({ width: 0, height: 0 }, { width: 0, height: 0 }, false).then(res => { firstFooterList[0][0].active = !state.enableMicr
// if (res.length) { firstFooterList[0][1].title = state.enableCamera ? '关闭视频' : '开启视频'
// let row = res.find((item: any) => item.sourceId === i.sourceId) firstFooterList[0][1].active = !state.enableCamera
// if (!row) { agora.muteLocalVideoStream(userInfo, state.enableMicr, state.enableCamera)
// stopScreenCapture() setFooterList(firstFooterList)
// setSharedScreenItem('') setTimeout(async () => {
// allUserLook(userInfo.uid, userInfo.userName) const setting = await JSON.parse(storage.getItem('setting') as string);
// } const stateInfo = await JSON.parse(storage.getItem('stateInfo') as string);
// } if (stateInfo && setting.isRecordingTips && !recorder) {
// }) setRecorder((data: any) => {
// } if (!data) {
// return i confirm({
// }) title: '提示',
// }, 3000) icon: <ExclamationCircleFilled />,
content: `是否录制本次会议?`,
centered: true,
okText: '确定',
cancelText: '取消',
async onOk() {
if (stateInfo) {
changeStatusList({
title: '录制'
}, 1, 3)
} else {
message.error('当前不在会议室!')
}
},
onCancel() {
}
})
}
return data
})
}
}, 10000);
return () => { return () => {
window.removeEventListener('customStorageChange', handleCustomStorageChange); window.removeEventListener('customStorageChange', handleCustomStorageChange);
window.removeEventListener('wheel', handleWheelChange); window.removeEventListener('wheel', handleWheelChange);
clearInterval(time) clearInterval(time)
// clearInterval(getDesktopCapturerVideoTime) channel.close();
}; };
}, []); }, []);
@ -262,6 +485,17 @@ const Meeting: React.FC = () => {
} }
}, [currentEffective]); }, [currentEffective]);
useEffect(() => {
if (chatList.length) {
window.electron.windowHandleMessage({
key: 'chatBigWindow',
parmes: {
chatList,
}
})
}
}, [chatList]);
useEffect(() => { useEffect(() => {
let currentVideoUserItem = roomUserList.find((item: any) => item.uid === currentVideoId || item.screenShareId === currentVideoId) let currentVideoUserItem = roomUserList.find((item: any) => item.uid === currentVideoId || item.screenShareId === currentVideoId)
if (currentVideoUserItem) { if (currentVideoUserItem) {
@ -312,6 +546,12 @@ const Meeting: React.FC = () => {
setNoViewChatList(storageNoViewChatList) setNoViewChatList(storageNoViewChatList)
} }
setChatList((newChatList: any) => [...newChatList, item]) setChatList((newChatList: any) => [...newChatList, item])
window.electron.windowHandleMessage({
key: 'chatSmallWindow',
parmes: {
chatListIten: item,
}
})
setStatusList((res: any) => { setStatusList((res: any) => {
if (!res.userChatList) { if (!res.userChatList) {
api.open({ api.open({
@ -407,14 +647,14 @@ const Meeting: React.FC = () => {
message.success(`操作成功`) message.success(`操作成功`)
await agora.updateChannelMediaOptions(item.user.isRoomManager) await agora.updateChannelMediaOptions(item.user.isRoomManager)
await postOpenMicrApi(item.user.isRoomManager, userInfo.uid, false) await postOpenMicrApi(item.user.isRoomManager, userInfo.uid, false)
await postOpenCameraApi(item.user.isRoomManager, userInfo.uid) await postOpenCameraApi(false, userInfo.uid) // 不管身份如何改变都关闭摄像头
await stopScreenCapture() await stopScreenCapture()
} else { } else {
message.success(`${item.user.userName}已结束发言`) message.success(`${item.user.userName}已结束发言`)
} }
} else { } else {
if (item.user.uid === userInfo.uid) { if (item.user.uid === userInfo.uid) {
if (!item.user.isRoomManager) { if (item.user.isRoomManager) {
await agora.allLeaveChannelEx() await agora.allLeaveChannelEx()
} }
message.success(`管理员${item.user.isRoomManager ? '设置' : '取消'}您为发言人`) message.success(`管理员${item.user.isRoomManager ? '设置' : '取消'}您为发言人`)
@ -426,7 +666,7 @@ const Meeting: React.FC = () => {
postOpenMicrApi(item.user.isRoomManager, userInfo.uid, false) postOpenMicrApi(item.user.isRoomManager, userInfo.uid, false)
} else { } else {
postOpenMicrApi(item.user.isRoomManager, userInfo.uid, false) postOpenMicrApi(item.user.isRoomManager, userInfo.uid, false)
postOpenCameraApi(item.user.isRoomManager, userInfo.uid) postOpenCameraApi(false, userInfo.uid)
} }
return '' return ''
}) })
@ -440,6 +680,17 @@ const Meeting: React.FC = () => {
break; break;
// 申请发言 // 申请发言
case 'ApplyToSpeak': case 'ApplyToSpeak':
setIsScreenCapture(bool => {
if (bool) {
window.electron.setChildWindowShow({
key: 'noticeWindow',
bool: true
})
channel.postMessage({
type: 'noticeItem',
noticeItem: item
});
} else {
api.open({ api.open({
message: '', message: '',
description: <div> description: <div>
@ -493,6 +744,9 @@ const Meeting: React.FC = () => {
showProgress: true, showProgress: true,
pauseOnHover: false, pauseOnHover: false,
}); });
}
return bool
})
break; break;
// 管理员查看随机用户 // 管理员查看随机用户
case 'Watch': case 'Watch':
@ -560,8 +814,13 @@ const Meeting: React.FC = () => {
case 'ShowDriverList': case 'ShowDriverList':
if (item.driversJsonString) { if (item.driversJsonString) {
const data = JSON.parse(item.driversJsonString); const data = JSON.parse(item.driversJsonString);
const isOpen = await getKeyOpenChildWindow('shareScreenWindow')
if (isOpen) {
channel.postMessage({ type: 'showDriverList', showDriverList: data })
} else {
equipmentManagementRef.current.setData(data) equipmentManagementRef.current.setData(data)
} }
}
break; break;
} }
}) })
@ -639,6 +898,27 @@ const Meeting: React.FC = () => {
observerObject.observe(element); observerObject.observe(element);
}); });
} }
window.electron.windowHandleMessage({
key: 'shareScreenWindow',
parmes: {
footerList,
type: 'footerList'
}
})
window.electron.windowHandleMessage({
key: 'shareScreenWindow',
parmes: {
roomUserList,
type: 'roomUserList'
}
})
window.electron.windowHandleMessage({
key: 'userListWindow',
parmes: {
roomUserList,
type: 'roomUserList'
}
})
return () => { return () => {
elements.forEach(element => { elements.forEach(element => {
observer?.unobserve(element); observer?.unobserve(element);
@ -701,6 +981,10 @@ const Meeting: React.FC = () => {
} }
}, },
onAudioVolumeIndication: async (speakers: AudioVolumeInfo[]) => { onAudioVolumeIndication: async (speakers: AudioVolumeInfo[]) => {
if (speakers.length) {
setSpeackUid((res: any) => {
return [...new Set([...res, ...(speakers.filter((item: any) => item.volume)).map(item => item.uid || userInfo.uid)])]
})
speakers.forEach((item: any) => { speakers.forEach((item: any) => {
let domMe = document.getElementById(`micr-item-${userInfo.uid}`); let domMe = document.getElementById(`micr-item-${userInfo.uid}`);
let dom = document.getElementById(`micr-${item.uid ? item.uid : userInfo.uid}`); let dom = document.getElementById(`micr-${item.uid ? item.uid : userInfo.uid}`);
@ -711,8 +995,16 @@ const Meeting: React.FC = () => {
if (domMe && !item.uid) { if (domMe && !item.uid) {
const percentage = (item.volume / 255) * 100 const percentage = (item.volume / 255) * 100
domMe.style.height = `${percentage}%` domMe.style.height = `${percentage}%`
window.electron.windowHandleMessage({
key: 'shareScreenWindow',
parmes: {
currentSpeakUserMe: percentage,
type: 'currentSpeakUserMe'
}
})
} }
}); });
}
}, },
onNetworkQuality: async (_connection: RtcConnection, remoteUid: number, _txQuality: QualityType, rxQuality: QualityType) => { onNetworkQuality: async (_connection: RtcConnection, remoteUid: number, _txQuality: QualityType, rxQuality: QualityType) => {
if (remoteUid === 0) { if (remoteUid === 0) {
@ -748,6 +1040,18 @@ const Meeting: React.FC = () => {
message.error('网络断开,请检查网络') message.error('网络断开,请检查网络')
} }
}, },
onLocalVideoStateChanged: async (_source: VideoSourceType, _state: LocalVideoStreamState, reason: LocalVideoStreamReason) => {
if (reason === 12) {
setIsScreenCapture(bool => {
if (bool) {
stopScreenCapture()
setSharedScreenItem('')
allUserLook(userInfo.uid, userInfo.userName)
}
return bool
})
}
}
}) })
if (state.enableCamera) { if (state.enableCamera) {
await agora.startCameraCapture() await agora.startCameraCapture()
@ -847,8 +1151,19 @@ const Meeting: React.FC = () => {
setRoomUserList((res: any) => { setRoomUserList((res: any) => {
let userItem = res.find((row: any) => row.uid === item.user.uid) let userItem = res.find((row: any) => row.uid === item.user.uid)
if (userItem) { if (userItem) {
for (const key in item.user) { for (const keys in item.user) {
userItem[key] = item.user[key]; if (keys !== 'enableCamera' && keys !== 'enableMicr') {
userItem[keys] = item.user[keys];
}
}
if (key === 'OperCamera') {
userItem.enableCamera = item.user.enableCamera;
if (userItem.uid === userInfo.uid) {
userItem.enableCamera ? agora.startCameraCapture() : agora.stopCameraCapture()
}
}
if (key === 'OperMicr') {
userItem.enableMicr = item.user.enableMicr;
} }
userItem.isAdmin = role.ID.includes(item.user.roleId) || item.user.isRoomManager; userItem.isAdmin = role.ID.includes(item.user.roleId) || item.user.isRoomManager;
refreshVideoView(userItem) refreshVideoView(userItem)
@ -1075,12 +1390,12 @@ const Meeting: React.FC = () => {
await allUserLook(userInfo.uid, userInfo.userName) await allUserLook(userInfo.uid, userInfo.userName)
break; break;
case '静音': case '静音':
await postOpenMicr(false, user.uid) await postOpenMicr(false, userInfo.uid)
break; break;
case '解除静音': case '解除静音':
await getUserRoomInfo().then(async (res) => { await getUserRoomInfo().then(async (res) => {
if (res) { if (res) {
await postOpenMicr(true, user.uid) await postOpenMicr(true, userInfo.uid)
} else { } else {
if (!isClicked) { if (!isClicked) {
setCurrentRequestSpeakType('audio') setCurrentRequestSpeakType('audio')
@ -1092,12 +1407,12 @@ const Meeting: React.FC = () => {
}) })
break; break;
case '关闭视频': case '关闭视频':
await postOpenCamera(false, user.uid) await postOpenCamera(false, userInfo.uid)
break; break;
case '开启视频': case '开启视频':
await getUserRoomInfo().then(async (res) => { await getUserRoomInfo().then(async (res) => {
if (res) { if (res) {
await postOpenCamera(true, user.uid) await postOpenCamera(true, userInfo.uid)
} else { } else {
if (!isClicked) { if (!isClicked) {
setCurrentRequestSpeakType('video') setCurrentRequestSpeakType('video')
@ -1259,6 +1574,52 @@ const Meeting: React.FC = () => {
setIsSharedScreenModal(false) setIsSharedScreenModal(false)
await agora.setDesktopCapturerVideo(sharedScreenItem, isComputerAudio, isFluencyPriority) await agora.setDesktopCapturerVideo(sharedScreenItem, isComputerAudio, isFluencyPriority)
await allUserLook(user.screenShareId, user.userName) await allUserLook(user.screenShareId, user.userName)
const isOpen = await getKeyOpenChildWindow('shareScreenWindow')
setIsScreenCapture(true)
window.electron.setViewStatus('hide')
if (!isOpen) {
window.electron.createChildWindow({
url: location.origin + `/#/noticeWindow`,
width: 388,
height: 150,
key: 'noticeWindow',
show: true,
})
window.electron.createChildWindow({
url: location.origin + `/#/shareScreenWindow`,
width: 400,
height: 80,
key: 'shareScreenWindow',
show: true,
})
window.electron.createChildWindow({
url: location.origin + `/#/chatSmallWindow`,
width: 200,
height: 150,
key: 'chatSmallWindow',
show: true,
})
window.electron.createChildWindow({
url: location.origin + `/#/currentSpeakUserWindow`,
width: 200,
height: 30,
key: 'currentSpeakUserWindow',
show: true,
})
window.electron.createChildWindow({
url: location.origin + `/#/chatBigWindow`,
width: 540,
height: 640,
key: 'chatBigWindow',
})
window.electron.createChildWindow({
url: location.origin + `/#/userListWindow`,
width: 440,
height: 540,
key: 'userListWindow',
})
setKeyOpenChildWindow('shareScreenWindow', true)
}
} else { } else {
message.error('请选择应用!') message.error('请选择应用!')
} }
@ -1267,7 +1628,9 @@ const Meeting: React.FC = () => {
const getDesktopCapturerVideo = (): void => { const getDesktopCapturerVideo = (): void => {
agora.getDesktopCapturerVideo({ width: 300, height: 300 }, { width: 300, height: 300 }, true).then((res: any) => { agora.getDesktopCapturerVideo({ width: 300, height: 300 }, { width: 300, height: 300 }, true).then((res: any) => {
res.forEach((item: any, index: number) => { res.forEach((item: any, index: number) => {
if (item.type === 1) {
item.sourceTitle = '桌面' + (index + 1) item.sourceTitle = '桌面' + (index + 1)
}
if (item.thumbImage.buffer) { if (item.thumbImage.buffer) {
item.thumbnailUrl = thumbImageBufferToBase64(item.thumbImage) item.thumbnailUrl = thumbImageBufferToBase64(item.thumbImage)
} }
@ -1301,6 +1664,25 @@ const Meeting: React.FC = () => {
agora.stopScreenCapture() agora.stopScreenCapture()
footerListTemplate[1][0].title = '共享屏幕' footerListTemplate[1][0].title = '共享屏幕'
setFooterList(footerListTemplate) setFooterList(footerListTemplate)
window.electron.closeChildWindow('shareScreenWindow')
setKeyOpenChildWindow('shareScreenWindow', false)
setIsScreenCapture(bool => {
if (bool) {
window.electron.setViewStatus('show')
window.electron.getWindowSize().then((res: any) => {
window.electron.setMainWindowSize({
width: Math.ceil(res.width / 1.5),
height: Math.ceil(res.height / 1.3),
})
window.electron.getIsMaximized().then((b: boolean) => {
if (!b) {
window.electron.setViewStatus('maximize')
}
})
})
}
return false
})
} }
// 获取房间用户 // 获取房间用户
const getRoomUser = async (): Promise<void> => { const getRoomUser = async (): Promise<void> => {
@ -1325,6 +1707,11 @@ const Meeting: React.FC = () => {
case 'meetingMode': case 'meetingMode':
setMeetingMode(e.value) setMeetingMode(e.value)
break; break;
case 'quitMeeting':
if (e.value) {
setQuitMeetingModal(true)
}
break;
case 'reconnect': case 'reconnect':
if (e.value == true) { if (e.value == true) {
await onInvoke('joinChannel', { await onInvoke('joinChannel', {
@ -1342,6 +1729,12 @@ const Meeting: React.FC = () => {
userId: userInfo.uid userId: userInfo.uid
}) })
} }
setIsScreenCapture(bool => {
if (bool) {
allUserLook(userItem.screenShareId, userItem.userName)
}
return bool
})
return res return res
}) })
@ -1357,12 +1750,19 @@ const Meeting: React.FC = () => {
roomNum: state.channelId, roomNum: state.channelId,
msg: msg, msg: msg,
}) })
setChatList((newChatList: any) => [...newChatList, { let item = {
uid: user.uid, uid: userInfo.uid,
userName: user.userName, userName: userInfo.userName,
message: msg, message: msg,
timestamp: +new Date() timestamp: +new Date()
}]) }
setChatList((newChatList: any) => [...newChatList, item])
window.electron.windowHandleMessage({
key: 'chatSmallWindow',
parmes: {
chatListIten: item,
}
})
setTextMsg(''); setTextMsg('');
chatScrollBotton() chatScrollBotton()
} else { } else {
@ -1783,7 +2183,7 @@ const Meeting: React.FC = () => {
}) })
} }
}} }}
>{item.isRoomManager ? '取消发言人' : '设为发言人'}</Button> : null} >{item.isRoomManager ? '取消发言' : '允许发言'}</Button> : null}
{item.isRoomManager ? <Button {item.isRoomManager ? <Button
type="primary" type="primary"
className='m-ant-btn' className='m-ant-btn'
@ -1950,7 +2350,7 @@ const Meeting: React.FC = () => {
{role.ID.includes(item.roleId) || item.isRoomManager ? <div> {role.ID.includes(item.roleId) || item.isRoomManager ? <div>
<img src={item.enableMicr ? ImageUrl.icon22 : ImageUrl.icon22Active} alt="" onClick={() => { <img src={item.enableMicr ? ImageUrl.icon22 : ImageUrl.icon22Active} alt="" onClick={() => {
postOpenMicr(!item.enableMicr, item.uid) postOpenMicr(!item.enableMicr, item.uid)
}} title={item.enableMicr ? '静音' : '解除音'} /> }} title={item.enableMicr ? '静音' : '解除音'} />
</div> : null} </div> : null}
{role.ID.includes(item.roleId) || item.isRoomManager ? <div> {role.ID.includes(item.roleId) || item.isRoomManager ? <div>
<img src={item.enableCamera ? ImageUrl.icon23 : ImageUrl.icon23Active} alt="" onClick={() => { <img src={item.enableCamera ? ImageUrl.icon23 : ImageUrl.icon23Active} alt="" onClick={() => {
@ -1959,28 +2359,7 @@ const Meeting: React.FC = () => {
</div> : null} </div> : null}
{item.uid !== user.uid && role.ID.includes(user.roleId) ? <div> {item.uid !== user.uid && role.ID.includes(user.roleId) ? <div>
<Popover placement="left" title={''} content={ <Popover placement="left" title={''} content={
<div> <div style={{ width: '100px' }}>
{!role.ID.includes(item.roleId) ? <Button
type="primary"
className='m-ant-btn'
style={{ marginBottom: '10px', width: '100%' }}
size={'small'}
onClick={() => {
if (item.isRoomManager) {
DeleteRoomManager({
roomId: state.roomId,
roomNum: state.channelId,
userId: item.uid
})
} else {
postRoomManager({
roomId: state.roomId,
roomNum: state.channelId,
userId: item.uid
})
}
}}
>{item.isRoomManager ? '取消发言人' : '设为发言人'}</Button> : null}
<Button <Button
type="primary" type="primary"
className='m-ant-btn' className='m-ant-btn'
@ -2000,12 +2379,29 @@ const Meeting: React.FC = () => {
></Button> ></Button>
</div> </div>
}> }>
<EllipsisOutlined style={{ <EllipsisOutlined style={{ color: '#fff', fontSize: '20px' }} />
color: '#fff',
fontSize: '20px'
}} />
</Popover> </Popover>
</div> : null} </div> : null}
{item.uid !== user.uid && !role.ID.includes(item.roleId) && role.ID.includes(user.roleId) ? <div onClick={() => {
if (item.isRoomManager) {
DeleteRoomManager({
roomId: state.roomId,
roomNum: state.channelId,
userId: item.uid
})
} else {
postRoomManager({
roomId: state.roomId,
roomNum: state.channelId,
userId: item.uid
})
}
}}>
{!item.isRoomManager ?
<img src={ImageUrl.icon50} alt="" title='允许发言' /> :
<img src={ImageUrl.icon51} alt="" title='取消发言' />
}
</div> : null}
</div> </div>
</div> : null </div> : null
) )
@ -2092,7 +2488,7 @@ const Meeting: React.FC = () => {
} }
}) })
}} }}
>{roomUserItem.isRoomManager ? '取消发言人' : '设为发言人'}</Button> : null} >{roomUserItem.isRoomManager ? '取消发言' : '允许发言'}</Button> : null}
{roomUserItem.isRoomManager ? <Button {roomUserItem.isRoomManager ? <Button
type="primary" type="primary"
className='m-ant-btn' className='m-ant-btn'
@ -2419,6 +2815,41 @@ const Meeting: React.FC = () => {
</div> </div>
</div> </div>
</Modal> </Modal>
<Modal
title=""
open={quitMeetingModal}
footer={null}
closable={false}
centered
width={'190px'}
>
<div>
<div className='meetingContentFooterPopover'>
{role.ID.includes(user.roleId) ?
<Popconfirm
title="提示"
description={`结束会议后,所有人将退出,是否结束?`}
onConfirm={async () => {
await GetLeaveAll({
roomNum: state.channelId,
})
}}
onCancel={() => {
}}
okText="结束"
cancelText="取消"
>
<div className='meetingContentFooterPopoverDel'></div>
</Popconfirm>
: null}
<div className='meetingContentFooterPopoverDefault' onClick={async () => {
await leaveChannel()
}}></div>
<div className='meetingContentFooterPopoverCancel' onClick={() => { setQuitMeetingModal(false) }}></div>
</div>
</div>
</Modal>
<SharedFilesModel ref={sharedFilesModelRef} /> <SharedFilesModel ref={sharedFilesModelRef} />
<SpeakerModeModal ref={speakerModeModalRef} /> <SpeakerModeModal ref={speakerModeModalRef} />
<InvitingPersonnelModal ref={invitingPersonnelRef} /> <InvitingPersonnelModal ref={invitingPersonnelRef} />

View File

@ -1,45 +0,0 @@
.shareScreenWindow {
background-color: #07090B;
color: white;
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
.shareScreenWindowTitle {
font-size: 12px;
flex-shrink: 0;
padding: 4px;
box-sizing: border-box;
background-color: lighten(#07090B, 4%);
}
.shareScreenWindowContent {
flex-grow: 1;
display: flex;
align-items: center;
justify-content: space-between;
.shareScreenWindowContentList {
display: flex;
flex-grow: 1;
justify-content: space-between;
padding: 0 20px;
>div {
display: flex;
flex-direction: column;
align-items: center;
cursor: pointer;
>img {
height: 24px;
}
>span {
font-size: 12px;
}
}
}
}
}

View File

@ -1,91 +0,0 @@
import styles from '@/page/ShareScreenWindow/index.module.scss'
import ImageUrl from '@/utils/package/imageUrl';
import { Button } from 'antd';
import { useEffect, useState } from "react";
// window.electron.createChildWindow({
// url: location.origin + `/#/shareScreenWindow`,
// width: 500,
// height: 70,
// key: 'shareScreenWindow',
// })
const ShareScreenWindow: React.FC = () => {
const [footerList, setFooterList] = useState<any>([
{
title: '静音',
icon: ImageUrl.icon22,
iconActive: ImageUrl.icon22Active,
active: false,
select: false,
},
{
title: '关闭视频',
icon: ImageUrl.icon23,
iconActive: ImageUrl.icon23Active,
active: false,
select: false,
},
{
title: '成员列表',
icon: ImageUrl.icon30,
iconSelect: ImageUrl.icon30Select,
active: false,
select: false,
},
{
title: '聊天',
icon: ImageUrl.icon31,
iconSelect: ImageUrl.icon31Select,
active: false,
select: false,
},
{
title: '设置',
icon: ImageUrl.icon28,
iconSelect: ImageUrl.icon28Select,
active: false,
select: false,
},
{
title: '录制',
icon: ImageUrl.icon27,
iconSelect: ImageUrl.icon27Select,
iconActive: ImageUrl.icon27Active,
active: false,
select: false,
},
])
useEffect(() => {
}, []);
return (
<>
<div className={`${styles.shareScreenWindow} drag`}>
<div className={styles.shareScreenWindowTitle}>0238 </div>
<div className={styles.shareScreenWindowContent}>
<div className={styles.shareScreenWindowContentList}>
{footerList.map((item: any, index: number) => {
return (
<div
onClick={() => {
console.log(item, index)
}}
key={index}>
{item.select ? <img src={item.iconSelect} alt="" /> : <img src={item.active ? item.iconActive : item.icon} alt="" />}
<span>{item.title}</span>
</div>
)
})}
</div>
<Button type="primary" style={{ backgroundColor: '#FF5219', marginRight: '14px' }} onClick={() => {
}}>
</Button>
</div>
</div>
</>
)
}
export default ShareScreenWindow

13
src/render.d.ts vendored
View File

@ -2,7 +2,7 @@
export interface IElectronAPI { export interface IElectronAPI {
setMainWindowSize: (config: any) => void; setMainWindowSize: (config: any) => void;
getWindowSize: () => any; getWindowSize: () => any;
setViewStatus: (status: 'quit' | 'maximize' | 'minimize' | 'unmaximize' | 'hide') => void; setViewStatus: (status: 'quit' | 'maximize' | 'minimize' | 'unmaximize' | 'hide' | 'show') => void;
getIsMaximized: () => Promise<boolean>; getIsMaximized: () => Promise<boolean>;
setWriteText: (text: string) => void; setWriteText: (text: string) => void;
onQuit: (callBack: Function) => void; onQuit: (callBack: Function) => void;
@ -12,13 +12,22 @@ export interface IElectronAPI {
selectFilePath: (data?: any) => void selectFilePath: (data?: any) => void
onFilePath: (callBack: Function) => void; onFilePath: (callBack: Function) => void;
getSources: () => any; getSources: () => any;
quit: () => any; quit: (bool) => any;
downFile: (callBack: Function) => void; downFile: (callBack: Function) => void;
quitAndInstall: (callBack: Function) => void; quitAndInstall: (callBack: Function) => void;
isOpenWindows: (callBack: Function) => void;
getVersion: () => Promise<string>; getVersion: () => Promise<string>;
isVisible: () => Promise<string>;
setRegistry: (uuid: string) => any; setRegistry: (uuid: string) => any;
getRegistry: () => any; getRegistry: () => any;
createChildWindow: (config: any) => void; createChildWindow: (config: any) => void;
setChildWindow: (config: any) => void;
setChildWindowShow: (config: any) => void;
closeChildWindow: (key: string) => void;
mainWindowCenter: () => any;
mainWindowHide: () => any;
windowHandleMessage: (data: any) => {}
windowHandleMessageCallBack: (callBack: Function) => void;
} }
declare global { declare global {

View File

@ -15,7 +15,9 @@ import {
AudioVolumeInfo, AudioVolumeInfo,
UserOfflineReasonType, UserOfflineReasonType,
ConnectionStateType, ConnectionStateType,
ConnectionChangedReasonType ConnectionChangedReasonType,
LocalVideoStreamReason,
LocalVideoStreamState
} from "agora-electron-sdk"; } from "agora-electron-sdk";
import { GetRoomRtcToken, GetAgoraConf } from "@/api/Home/Index"; import { GetRoomRtcToken, GetAgoraConf } from "@/api/Home/Index";
import { storage } from '@/utils'; import { storage } from '@/utils';
@ -38,7 +40,9 @@ export const agora = {
await rtcEngine.initialize({ await rtcEngine.initialize({
appId: data, appId: data,
}); });
await agora.setDeviceManager(bool) if (bool) {
await agora.setDeviceManager()
}
} }
}, },
// 获取rtcEngine // 获取rtcEngine
@ -46,7 +50,7 @@ export const agora = {
return rtcEngine return rtcEngine
}, },
// 获取当前设备是否存在不存在就获取默认设备 // 获取当前设备是否存在不存在就获取默认设备
setDeviceManager: async (bool: boolean = false) => { setDeviceManager: async () => {
const setting = await JSON.parse(storage.getItem('setting') as string) const setting = await JSON.parse(storage.getItem('setting') as string)
// 摄像头 // 摄像头
if (setting.videoDeviceId) { if (setting.videoDeviceId) {
@ -99,19 +103,17 @@ export const agora = {
} }
setTimeout(async () => { setTimeout(async () => {
storage.setItem('setting', JSON.stringify(setting)) storage.setItem('setting', JSON.stringify(setting))
if (bool) { const settingData = await JSON.parse(storage.getItem('setting') as string)
const setting = await JSON.parse(storage.getItem('setting') as string) if (settingData.videoDeviceId) agora.setVideoDeviceManager(settingData.videoDeviceId) //指定摄像头头采集设备
if (setting.videoDeviceId) agora.setVideoDeviceManager(setting.videoDeviceId) //指定摄像头头采集设备 if (settingData.playBackDeviceId) agora.setPlaybackDevice(settingData.playBackDeviceId) //指定播放设备
if (setting.playBackDeviceId) agora.setPlaybackDevice(setting.playBackDeviceId) //指定播放设备 if (settingData.playBackVolume) agora.setPlaybackDeviceVolume(settingData.playBackVolume) // 设置播放设备音量
if (setting.playBackVolume) agora.setPlaybackDeviceVolume(setting.playBackVolume) // 设置播放设备音量 if (settingData.ecordingDeviceId) agora.setRecordingDevice(settingData.ecordingDeviceId) // 设置音频采集设备
if (setting.ecordingDeviceId) agora.setRecordingDevice(setting.ecordingDeviceId) // 设置音频采集设备 if (settingData.ecordingVolume) agora.setRecordingDeviceVolume(settingData.ecordingVolume) // 设置音频设备音量
if (setting.ecordingVolume) agora.setRecordingDeviceVolume(setting.ecordingVolume) // 设置音频设备音量 if (settingData.isAINoiseReduction) agora.setAINSMode(settingData.isAINoiseReduction, settingData.aINoiseReduction) // 设置ai降噪
if (setting.isAINoiseReduction) agora.setAINSMode(setting.isAINoiseReduction, setting.aINoiseReduction) // 设置ai降噪
}
}, 1000); }, 1000);
}, },
// 事件回调 // 事件回调
registerEventHandler: ({ onJoinChannelSuccess, onUserJoined, onUserOffline, onAudioVolumeIndication, onNetworkQuality, onRtcStats, onConnectionStateChanged }: any) => { registerEventHandler: ({ onJoinChannelSuccess, onUserJoined, onUserOffline, onAudioVolumeIndication, onNetworkQuality, onRtcStats, onConnectionStateChanged, onLocalVideoStateChanged }: any) => {
rtcEngine.registerEventHandler({ rtcEngine.registerEventHandler({
// 监听本地用户加入频道事件 // 监听本地用户加入频道事件
onJoinChannelSuccess: async (connection: RtcConnection, elapsed: number) => { onJoinChannelSuccess: async (connection: RtcConnection, elapsed: number) => {
@ -153,6 +155,10 @@ export const agora = {
onConnectionStateChanged: async (connection: RtcConnection, state: ConnectionStateType, reason: ConnectionChangedReasonType) => { onConnectionStateChanged: async (connection: RtcConnection, state: ConnectionStateType, reason: ConnectionChangedReasonType) => {
await onConnectionStateChanged?.(connection, state, reason) await onConnectionStateChanged?.(connection, state, reason)
}, },
// 本地视频状态发生改变回调。
onLocalVideoStateChanged: async (source: VideoSourceType, state: LocalVideoStreamState, reason: LocalVideoStreamReason) => {
await onLocalVideoStateChanged?.(source, state, reason)
},
}); });
}, },
// 获取视图模式 // 获取视图模式
@ -165,7 +171,7 @@ export const agora = {
}, },
// 本地加入 // 本地加入
setupLocalVideo: async (item: any) => { setupLocalVideo: async (item: any) => {
if (item.view?.childNodes.length === 1) { if (item.view?.childNodes.length === 1 || item.type) {
await rtcEngine.setupLocalVideo({ await rtcEngine.setupLocalVideo({
renderMode: agora.getRrenderMode(item.uid), renderMode: agora.getRrenderMode(item.uid),
sourceType: item.sourceType, sourceType: item.sourceType,
@ -232,7 +238,7 @@ export const agora = {
}, },
// 加入频道 // 加入频道
joinChannel: async () => { joinChannel: async () => {
await rtcEngine.enableAudioVolumeIndication(100, 1, true) await rtcEngine.enableAudioVolumeIndication(100, 3, true)
await rtcEngine.joinChannel(option.token, option.channelId, option.uid); await rtcEngine.joinChannel(option.token, option.channelId, option.uid);
await rtcEngine.setDualStreamModeEx( await rtcEngine.setDualStreamModeEx(
SimulcastStreamMode.EnableSimulcastStream, SimulcastStreamMode.EnableSimulcastStream,
@ -323,8 +329,8 @@ export const agora = {
rtcEngine.muteRemoteVideoStream(uid, mute) rtcEngine.muteRemoteVideoStream(uid, mute)
}, },
// 销毁视频渲染dom // 销毁视频渲染dom
destroyRendererByConfig: async (uid: number) => { destroyRendererByConfig: async (uid: number, channelId: string) => {
await rtcEngine.destroyRendererByConfig(VideoSourceType.VideoSourceRemote, option.channelId + 'a', uid); await rtcEngine.destroyRendererByConfig(VideoSourceType.VideoSourceRemote, channelId, uid);
}, },
// ai降噪 // ai降噪
setAINSMode: async (enabled: boolean, mode: AudioAinsMode) => { setAINSMode: async (enabled: boolean, mode: AudioAinsMode) => {
@ -388,7 +394,7 @@ export const agora = {
}, },
// 桌面捕获音频和视频的媒体源的信息 // 桌面捕获音频和视频的媒体源的信息
getDesktopCapturerVideo: async (thumbSize: any, iconSize: any, includeScreen: boolean) => { getDesktopCapturerVideo: async (thumbSize: any, iconSize: any, includeScreen: boolean) => {
return await rtcEngine.getScreenCaptureSources(thumbSize, iconSize, includeScreen).filter((item: any) => item.type === 1) return await rtcEngine.getScreenCaptureSources(thumbSize, iconSize, includeScreen)
}, },
// 共享屏幕采集 // 共享屏幕采集
setDesktopCapturerVideo: async (targetSource: any, isComputerAudio: boolean, isFluencyPriority: boolean) => { setDesktopCapturerVideo: async (targetSource: any, isComputerAudio: boolean, isFluencyPriority: boolean) => {
@ -414,8 +420,6 @@ export const agora = {
{}, {},
{ {
windowFocus: true, windowFocus: true,
enableHighLight: true,
highLightColor: 0xFF99CC00,
...data ...data
} }
); );
@ -425,8 +429,6 @@ export const agora = {
{}, {},
{ {
windowFocus: true, windowFocus: true,
enableHighLight: true,
highLightColor: 0xFF99CC00,
...data ...data
} }
); );

View File

@ -71,6 +71,8 @@ import icon47Active from '@/assets/icon47-active.png'
import icon48 from '@/assets/icon48.png' import icon48 from '@/assets/icon48.png'
import icon48Select from '@/assets/icon48-select.png' import icon48Select from '@/assets/icon48-select.png'
import icon49 from '@/assets/icon49.png' import icon49 from '@/assets/icon49.png'
import icon50 from '@/assets/icon50.png'
import icon51 from '@/assets/icon51.png'
export default { export default {
loading, loading,
icon, icon,
@ -145,4 +147,6 @@ export default {
icon48, icon48,
icon48Select, icon48Select,
icon49, icon49,
icon50,
icon51,
} }

View File

@ -0,0 +1,15 @@
import storage from "./storage";
export const setKeyOpenChildWindow = async (key: string, bool: boolean) => {
const openChildWindow = await JSON.parse(storage.getItem('openChildWindow') as string)
openChildWindow[key] = bool;
if (key === 'shareScreenWindow' && !bool) {
for (const k in openChildWindow) {
openChildWindow[k] = false
}
}
storage.setItem('openChildWindow', JSON.stringify(openChildWindow))
};
export const getKeyOpenChildWindow = async (key: string): Promise<boolean> => {
const openChildWindow = await JSON.parse(storage.getItem('openChildWindow') as string)
return openChildWindow[key]
};

View File

@ -376,6 +376,7 @@ $pagination-hover-background-color: #5575F2;
.ant-message { .ant-message {
-webkit-app-region: no-drag; -webkit-app-region: no-drag;
} }
// ant-spin-fullscreen // ant-spin-fullscreen
.ant-spin-fullscreen { .ant-spin-fullscreen {
z-index: 10000; z-index: 10000;
@ -384,3 +385,7 @@ $pagination-hover-background-color: #5575F2;
.ant-picker-dropdown { .ant-picker-dropdown {
-webkit-app-region: no-drag; -webkit-app-region: no-drag;
} }
.ant-tabs {
-webkit-app-region: no-drag;
}

View File

@ -70,7 +70,9 @@ export default defineConfig({
AudioVolumeInfo, AudioVolumeInfo,
UserOfflineReasonType, UserOfflineReasonType,
ConnectionStateType, ConnectionStateType,
ConnectionChangedReasonType ConnectionChangedReasonType,
LocalVideoStreamState,
LocalVideoStreamReason
} = require("agora-electron-sdk") } = require("agora-electron-sdk")
export { export {
createAgoraRtcEngine, createAgoraRtcEngine,
@ -89,7 +91,9 @@ export default defineConfig({
AudioVolumeInfo, AudioVolumeInfo,
UserOfflineReasonType, UserOfflineReasonType,
ConnectionStateType, ConnectionStateType,
ConnectionChangedReasonType ConnectionChangedReasonType,
LocalVideoStreamState,
LocalVideoStreamReason
} }
`, `,
}) })