Compare commits

..

No commits in common. "3b03d6a8c36352e79d2a940e4c63472fb579be03" and "734dc7a41a911fc89aaa6ce0c59fd159a7bed9f3" have entirely different histories.

57 changed files with 670 additions and 1504 deletions

View File

@ -1,5 +1,5 @@
#基础API 绝对的
VITE_BASE_URL_API = 'https://meeting-api.23544.com/pc'
VITE_BASE_URL_API = 'http://192.168.2.9:5192'
VITE_BASE_URL_DRAW_API = 'http://192.168.2.9:6555'
#当前IP 相对的
VITE_BASE_CURRENT_API = '.'

58
main.js
View File

@ -16,6 +16,7 @@ const { autoUpdater, CancellationToken } = require('electron-updater');
const cancellationToken = new CancellationToken()
app.allowRendererProcessReuse = false;
let mainWindow = null;
let newWindow = null;
let isMaximized = false;
let env;
@ -129,9 +130,6 @@ app.on('ready', () => {
}
createWindow()
updateHandle() // 检查更新
setInterval(() => {
updateHandle() // 每一小时检查更新
}, 1000 * 60 * 60)
createTray()
// 获取当前脚本所在目录的绝对路径
const currentDirectory = __dirname;
@ -144,11 +142,11 @@ app.on('ready', () => {
}
// 监听f12打开控制台
mainWindow.webContents.on('before-input-event', (event, input) => {
// if (env === 'development') {
if (env === 'development') {
if (input.key === 'F12') {
mainWindow.webContents.openDevTools()
}
// }
}
});
// 监听移动
mainWindow.on('move', () => {
@ -245,12 +243,6 @@ app.on('ready', () => {
}
}
});
// 获取桌面大小
ipcMain.handle('getWindowSize', (event, config) => {
const primaryDisplay = screen.getPrimaryDisplay()
const { width, height } = primaryDisplay.workAreaSize
return { width, height }
});
// 设置桌面应用基础属性
ipcMain.handle('setMainWindowSize', (event, config) => {
// 设置最小窗口尺寸
@ -271,6 +263,46 @@ app.on('ready', () => {
const y = Math.round((display.workArea.height - mainWindow.getSize()[1]) / 2);
mainWindow.setPosition(x, y);
});
// 打开新窗口
ipcMain.handle('oepnWindow', (event, data) => {
if (newWindow) {
newWindow.focus();
} else {
newWindow = new BrowserWindow({
width: 1200,
height: 800,
minWidth: 1200,
minHeight: 800,
webPreferences: {
contextIsolation: false,
nodeIntegration: true,
enableRemoteModule: true,
nodeIntegrationInWorker: true,
allowMediaDevices: true,
preload: path.join(__dirname, 'preload.js')
},
frame: false,
backgroundColor: '#00000000',
transparent: true,
});
newWindow.loadURL(data.url);
newWindow.focus();
newWindow.webContents.on('before-input-event', (event, input) => {
if (env === 'development') {
if (input.key === 'F12') {
newWindow.webContents.openDevTools()
}
}
});
}
});
// 关闭会议监控窗口
ipcMain.handle('closeMonitorWindow', () => {
if (newWindow) {
newWindow.close();
newWindow = null
}
});
}
});
// 检测更新在你想要检查更新的时候执行renderer事件触发后的操作自行编写
@ -281,9 +313,9 @@ function updateHandle() {
error: '检查更新出错',
checking: '正在检查更新……',
updateAva: '检测到新版本,正在下载……',
updateNotAva: '已经是最新版本,不用更新'
updateNotAva: '现在使用的就是最新版本,不用更新'
}
autoUpdater.setFeedURL('https://meeting-api.23544.com/meeting/update')
autoUpdater.setFeedURL('https://update.23544.com/metting')
autoUpdater.autoDownload = false // 不自动下载安装包
autoUpdater.autoInstallOnAppQuit = false // 不自动安装
autoUpdater.on('error', function (error) {

View File

@ -1,10 +1,10 @@
{
"name": "WGShare.Metting",
"private": true,
"version": "0.1.14",
"version": "0.0.1",
"main": "main.js",
"authors": "yj",
"description": "智汇享",
"description": "test",
"scripts": {
"dev": "concurrently \"electron . --env=development\" \"cross-env BROWSER=none vite\"",
"test": "concurrently \"electron . --env=test\" \"cross-env BROWSER=none vite\"",
@ -69,7 +69,7 @@
"publish": [
{
"provider": "generic",
"url": "https://meeting-api.23544.com/meeting/update"
"url": "https://update.23544.com/metting"
}
],
"files": [
@ -77,7 +77,6 @@
],
"win": {
"icon": "build/start.ico",
"requestedExecutionLevel": "highestAvailable",
"target": [
{
"target": "nsis",
@ -100,9 +99,7 @@
"createDesktopShortcut": true,
"createStartMenuShortcut": true,
"deleteAppDataOnUninstall": true,
"shortcutName": "智汇享",
"allowElevation": true,
"perMachine":true
"shortcutName": "智汇享"
}
}
}

View File

@ -5,10 +5,6 @@ window.electron = {
setMainWindowSize: (config) => {
ipcRenderer.invoke('setMainWindowSize', { ...config })
},
// 获取窗口大小
getWindowSize: (config) => {
return ipcRenderer.invoke('getWindowSize')
},
// 设置窗口状态
setViewStatus: (status) => {
ipcRenderer.invoke('setViewStatus', status)
@ -64,5 +60,13 @@ window.electron = {
// 下载文件
downFile: (callback) => {
ipcRenderer.on('downFile', callback)
},
// 打开新窗口
oepnWindow: (data) => {
ipcRenderer.invoke('oepnWindow', data)
},
// 关闭会议监控窗口
closeMonitorWindow: () => {
ipcRenderer.invoke('closeMonitorWindow')
}
}

View File

@ -17,6 +17,7 @@ import { PostLogin } from "@/api/Login";
import { agora } from "@/utils/package/agora";
import QuitTips from "@/components/QuitTips";
import { GetLeave } from "@/api/Meeting";
import UserVideo from "./page/UserVideo";
import path from "path";
const fs = require('fs').promises;
const { exec } = require('child_process');
@ -32,6 +33,7 @@ const App: React.FC = () => {
});
const [spinning, setSpinning] = useState(false);
const [isState, setIsState] = useState(true);
if (location.href.indexOf('/userVideo') === -1) {
useEffect(() => {
let userInfo = JSON.parse(storage.getItem('user') as string)
let loginInfo = JSON.parse(storage.getItem('login') as string)
@ -43,7 +45,6 @@ const App: React.FC = () => {
}).then(async (res) => {
if (res.code === 200) {
storage.setItem('user', JSON.stringify(res.data))
storage.setItem('userLogin', true)
toSrc('/home')
await startSignalr()
} else {
@ -120,8 +121,6 @@ const App: React.FC = () => {
shareFilesPath: path.resolve(__dirname, '../../Downloads/') + '\\', //共享文件保存路径
isShareSavePath: true, //是否下载钱询问每个文件保存的位置
closeSetting: 'hide', //关闭按钮设置
isAINoiseReduction: true, //是否开启ai降噪
aINoiseReduction: 1, // 降噪模式
}))
}
}, [])
@ -151,7 +150,11 @@ const App: React.FC = () => {
if (location.href.indexOf('/login') !== -1) {
onStop()
}
if (location.href.indexOf('/meeting') === -1) {
window.electron.closeMonitorWindow()
}
}, [navigate])
}
useEffect(() => {
document.addEventListener('keydown', (event) => {
if (event.key === 'F11') {
@ -197,22 +200,20 @@ const App: React.FC = () => {
})
}
const toSrc = (path: string): void => {
window.electron.getWindowSize().then((res: any) => {
switch (path) {
case '/login':
storage.removeItem('user')
storage.setItem('userLogin', false)
navigate('/login')
break;
case '/home':
window.electron.setMainWindowSize({
width: Math.ceil(res.width / 1.5),
height: Math.ceil(res.height / 1.3),
width: 1200,
height: 800,
})
navigate('/home')
break;
}
})
};
const leaveChannel = async (bool?: boolean): Promise<void> => {
if (location.hash.indexOf('/meeting') === 1) {
@ -232,10 +233,6 @@ const App: React.FC = () => {
if (Boolean(e.value)) {
onEventSignalr()
}
} else if (e.key === 'userLogin') {
if (!Boolean(e.value)) {
navigate('/login')
}
}
};
@ -262,6 +259,7 @@ const App: React.FC = () => {
</Route>
<Route path='/login' element={<Login />} />
<Route path='/meeting' element={<Meeting />} />
<Route path='/userVideo' element={<UserVideo />} />
<Route path='*' element={<NotFound />} />
</Routes>
<Spin spinning={spinning} fullscreen />

View File

@ -5,17 +5,12 @@ export const GetRoom = (data: { pageIndex: number, pageSize: number }) =>
method: 'get'
})
export const PostRoom = (data: any) =>
export const PostRomm = (data: any) =>
request({
url: `/home/room`,
method: 'post',
data,
})
export const DeleteRoom = (roomId: string) =>
request({
url: `/home/room?roomId=${roomId}`,
method: 'delete',
})
export const GetCheckoutRoomNum = (roomNum: string) =>
request({
@ -35,15 +30,3 @@ export const GetRoomInfo = (roomNum: string) =>
method: 'get',
})
export const GetAgoraConf = () =>
request({
url: `/home/agora-conf`,
method: 'get',
})
export const GetRecord = (beginTimestamp: number, endTimestamp: number, roomNum: string) =>
request({
url: `/home/record?beginTimestamp=${beginTimestamp}&endTimestamp=${endTimestamp}&roomNum=${roomNum}`,
method: 'get',
})

View File

@ -38,10 +38,3 @@ export const GetRoleDpList = () =>
url: `/pub/role-dp-list`,
method: 'get',
})
export const PostUserImport = (data: any) =>
request({
url: `/user/import`,
method: 'post',
data
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 622 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 477 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 677 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 530 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 650 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 495 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 675 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 977 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 760 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 892 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 572 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 769 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 973 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 641 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 963 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 647 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 700 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

View File

@ -1,23 +0,0 @@
.equipmentManagement {
padding: 10px;
box-sizing: border-box;
>div:nth-child(1) {
>div {
display: flex;
align-items: center;
margin-bottom: 10px;
>span {
color: #EEEEEE;
font-size: 16px;
margin-right: 10px;
}
}
}
>div:nth-child(2) {
display: flex;
justify-content: center;
margin-top: 20px;
}
}

View File

@ -1,103 +0,0 @@
import styles from '@/components/EquipmentManagement/index.module.scss'
import { onInvoke } from '@/utils/package/signalr';
import { Button, Modal, Select, Slider, message } from 'antd';
import { useState, useImperativeHandle, forwardRef } from "react";
const EquipmentManagement = forwardRef((_props: any, ref: any) => {
useImperativeHandle(ref, () => ({
changeModal: async (uid: string, userName: string) => {
setCallerUid(uid)
setDeviceInfo({})
await onInvoke('getDrivers', {
uid
})
setUserName(userName)
setEquipmentManagementModal(true)
},
setData: (data: any) => {
setDeviceInfo(data)
}
}))
const [equipmentManagementModal, setEquipmentManagementModal] = useState(false)
const [callerUid, setCallerUid] = useState('')
const [deviceInfo, setDeviceInfo] = useState<any>({})
const [userName, setUserName] = useState<any>({})
return (
<>
<Modal
title={`设备管理(${userName})`}
open={equipmentManagementModal}
footer={null}
centered
width={'500px'}
onCancel={() => {
setEquipmentManagementModal(false)
}}>
<div className={styles.equipmentManagement}>
<div>
<div>
<span></span>
<Select
placeholder={deviceInfo?.videoList?.length ? '请选择设备' : '未检测到摄像头'}
options={deviceInfo?.videoList} style={{ flexGrow: 1 }}
value={deviceInfo?.videoDeviceId} onChange={async (e) => {
setDeviceInfo({
...deviceInfo,
videoDeviceId: e
})
}} />
</div>
<div>
<span></span>
<Select
placeholder={deviceInfo?.ecordingList?.length ? '请选择设备' : '未检测到麦克风'}
options={deviceInfo?.ecordingList} style={{ flexGrow: 1 }}
value={deviceInfo?.ecordingDeviceId} onChange={async (e) => {
setDeviceInfo({
...deviceInfo,
ecordingDeviceId: e
})
}} />;
</div>
<div>
<span></span>
<Slider value={deviceInfo.ecordingVolume} style={{ flexGrow: 1 }} max={255}
onChange={async (e) => {
setDeviceInfo({
...deviceInfo,
ecordingVolume: e
})
}} disabled={!deviceInfo.ecordingDeviceId} />
</div>
<div>
<span></span>
<Select
placeholder={deviceInfo?.playBackList?.length ? '请选择设备' : '未检测到麦克风'}
options={deviceInfo?.playBackList} style={{ flexGrow: 1 }}
value={deviceInfo?.playBackDeviceId} onChange={async (e) => {
setDeviceInfo({
...deviceInfo,
playBackDeviceId: e
})
}} />;
</div>
</div>
<div>
<Button type="primary" className='m-ant-btn' onClick={async () => {
await onInvoke('setDrivers', {
uid: callerUid,
driversJsonString: JSON.stringify(deviceInfo)
})
setEquipmentManagementModal(false)
message.success('设置成功')
}}></Button>
<Button type="primary"
style={{ backgroundColor: '#31353A', marginLeft: '10px' }}
onClick={() => setEquipmentManagementModal(false)}></Button>
</div>
</div>
</Modal >
</>
)
})
export default EquipmentManagement

View File

@ -8,7 +8,6 @@ import { PostRefresh } from '@/api/Login';
import Avatar from '@/components/Avatar';
import { useNavigate } from 'react-router-dom';
import { agora } from '@/utils/package/agora';
import { role } from '@/config/role';
let time = null as any;
const JoinSetting = forwardRef((_props: any, ref: any) => {
useImperativeHandle(ref, () => ({
@ -72,7 +71,7 @@ const JoinSetting = forwardRef((_props: any, ref: any) => {
})
}
const getRoomRtcToken = async (roomNum: string, callBack: Function): Promise<void> => {
Promise.all([GetRoomRtcToken(roomNum), GetRoomRtcToken(roomNum + 'a')]).then(res => {
Promise.all([GetRoomRtcToken(roomNum), GetRoomRtcToken(roomNum + '1')]).then(res => {
if (res[0].code === 200 && res[1].code === 200) {
callBack({
token: res[0].data,
@ -85,7 +84,6 @@ const JoinSetting = forwardRef((_props: any, ref: any) => {
await PostRefresh(user.refresh_token).then(res => {
if (res.code === 200) {
storage.setItem('user', JSON.stringify(res.data))
storage.setItem('userLogin', true)
callBack(res.data)
}
})
@ -128,7 +126,7 @@ const JoinSetting = forwardRef((_props: any, ref: any) => {
{
joinRoomSettingForm.map((item, index) => {
return <div key={index} onClick={async () => {
if (role.ID.includes(user.roleId)) {
if (user.roleId === '1') {
let msg = '';
if (index === 0) {
await agora.getAudioMediaList().then(res => {

View File

@ -7,15 +7,14 @@ import {
VerticalAlignBottomOutlined
} from '@ant-design/icons';
import { Button, Input, message, Modal, Pagination, Progress, Table } from 'antd';
import { forwardRef, useEffect, useImperativeHandle, useState, useRef } from "react";
import { forwardRef, useEffect, useImperativeHandle, useState } from "react";
import { DeleteRoomFile, GetRoomFile, GetRoomFileDwUrl, GetRoomUpFileurl, GetRoomUserItem, PostRoomFile } from '@/api/Meeting';
import axios from 'axios';
import { useLocation } from 'react-router-dom';
import { storage } from '@/utils';
import StupWizard from '../StupWizard';
import { role } from '@/config/role';
const fs = require('fs').promises;
const { exec } = require('child_process');
const { Column } = Table
const SharedFilesModel = forwardRef((props: any, ref: any) => {
@ -27,10 +26,8 @@ const SharedFilesModel = forwardRef((props: any, ref: any) => {
setRoomUserItem(res.data)
}
})
getRoomFile()
}
}))
const stupWizardRef = useRef<any>();
const { state } = useLocation();
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
const [roomUserItem, setRoomUserItem] = useState<any>({})
@ -129,7 +126,7 @@ const SharedFilesModel = forwardRef((props: any, ref: any) => {
})
}
}} />
{roomUserItem && role.ID.includes(roomUserItem.roleId) || roomUserItem.isRoomManager ? <ProfileOutlined title={showRowSelection ? '取消框选' : '显示框选'} onClick={() => {
{roomUserItem && roomUserItem.roleId === '1' || roomUserItem.isRoomManager ? <ProfileOutlined title={showRowSelection ? '取消框选' : '显示框选'} onClick={() => {
setShowRowSelection(!showRowSelection)
}} style={{ color: showRowSelection ? '#5575F2' : 'white' }} /> : null}
{showRowSelection ? <DeleteOutlined title='删除' onClick={() => {
@ -313,11 +310,7 @@ const SharedFilesModel = forwardRef((props: any, ref: any) => {
}
} catch (error: any) {
if (error.code === 'ENOENT') {
message.error({
content: <div> <span style={{ color: '#606fc7', cursor: 'pointer' }} onClick={() => {
stupWizardRef.current.changeModal(4)
}}></span></div>,
})
message.error('文件夹不存在!')
return
} else {
message.error(error)
@ -330,6 +323,7 @@ const SharedFilesModel = forwardRef((props: any, ref: any) => {
})
}
})
}} />
{/* <FolderOutlined title='文件' style={{ color: '#FFA000', cursor: 'pointer', marginLeft: '10px' }} /> */}
</>
@ -350,7 +344,6 @@ const SharedFilesModel = forwardRef((props: any, ref: any) => {
</div>
</div>
</Modal>
<StupWizard ref={stupWizardRef} />
</>
)
})

View File

@ -1,6 +1,6 @@
import styles from '@/components/StupWizard/index.module.scss'
import ImageUrl from '@/utils/package/imageUrl';
import { Button, Checkbox, Empty, Input, message, Modal, Popover, Radio, Select, Slider, Space } from 'antd';
import { Button, Checkbox, Empty, Input, message, Modal, Popover, Radio, Select, Slider } from 'antd';
import { forwardRef, useEffect, useImperativeHandle, useState } from "react";
import { agora } from '@/utils/package/agora'
import { CloseOutlined, LoadingOutlined, QuestionCircleOutlined } from '@ant-design/icons';
@ -11,7 +11,7 @@ const fs = require('fs').promises;
const { exec } = require('child_process');
const StupWizard = forwardRef((props: any, ref: any) => {
useImperativeHandle(ref, () => ({
changeModal: (index: number = 0) => {
changeModal: () => {
if (location.hash.indexOf('/meeting') === -1) {
agora.init()
}
@ -19,7 +19,7 @@ const StupWizard = forwardRef((props: any, ref: any) => {
res.forEach((item: any) => {
item.active = false
});
res[index].active = true;
res[0].active = true;
return res
})
setIsStupWizard(true)
@ -243,8 +243,6 @@ const AudioComponents = () => {
ecordingActive: false,
ecordingVolume: 0,
autoEcordingVolume: true,
isAINoiseReduction: true,
aINoiseReduction: 1
});
const setting = JSON.parse(storage.getItem('setting') as string)
useEffect(() => {
@ -290,9 +288,7 @@ const AudioComponents = () => {
ecordingItem: setting.ecordingDeviceId,
ecordingVolume: setting.ecordingVolume,
playBackVolume: setting.playBackVolume,
autoEcordingVolume: setting.autoEcordingVolume,
isAINoiseReduction: setting.isAINoiseReduction,
aINoiseReduction: setting.aINoiseReduction,
autoEcordingVolume: setting.autoEcordingVolume
})
}
return (
@ -354,38 +350,6 @@ const AudioComponents = () => {
})
}} disabled={!audioDeviceManager.ecordingItem} />
</div>
<div>
<div>
<Checkbox checked={audioDeviceManager.isAINoiseReduction} onChange={(e) => {
setting.isAINoiseReduction = e.target.checked;
storage.setItem('setting', JSON.stringify(setting))
setAudioDeviceManager({
...audioDeviceManager,
isAINoiseReduction: e.target.checked
})
agora.setAINSMode(e.target.checked, audioDeviceManager.aINoiseReduction)
}}>
AI降噪
</Checkbox>
</div>
<div style={{ margin: '10px 0 0 20px' }}>
<Radio.Group onChange={(e) => {
setting.aINoiseReduction = e.target.value;
storage.setItem('setting', JSON.stringify(setting))
setAudioDeviceManager({
...audioDeviceManager,
aINoiseReduction: e.target.value
})
agora.setAINSMode(audioDeviceManager.isAINoiseReduction, e.target.value)
}} disabled={!audioDeviceManager.isAINoiseReduction} value={audioDeviceManager.aINoiseReduction}>
<Space direction="vertical">
<Radio value={0}></Radio>
<Radio value={1}></Radio>
<Radio value={2}></Radio>
</Space>
</Radio.Group>
</div>
</div>
{/* <div>
<Checkbox onChange={async (e) => {
setting.autoEcordingVolume = e.target.checked;

View File

@ -22,7 +22,7 @@ const UpdateModal = forwardRef((props: any, ref: any) => {
const [updateContent, setUpdateContent] = useState('') // 版本更新内容
function getContent() {
fetch(`https://meeting-api.23544.com/meeting/update/update.txt?t=${+new Date()}`) // 配置服务器地址
fetch(`https://update.23544.com/metting/update.txt?t=${+new Date()}`) // 配置服务器地址
.then(async response => {
if (response.status === 200) {
return setUpdateContent(await response.text())
@ -67,7 +67,7 @@ const UpdateModal = forwardRef((props: any, ref: any) => {
></Button>
<div className={styles.button2} onClick={() => setIsUpdateModal(false)}></div>
</div> : progress < 100 ?
<div style={{ margin: '20px 0' }}>
<div style={{ marginTop: '20px' }}>
{progress}%
<Flex gap="small" vertical>
<Progress percent={progress} showInfo={false} />
@ -75,7 +75,7 @@ const UpdateModal = forwardRef((props: any, ref: any) => {
</div> :
<Button type="primary"
onClick={() => window.electron.onDownload('2')}
style={{ width: '100%', height: '40px', margin: '20px 0' }}
style={{ width: '100%', height: '40px', marginTop: '20px' }}
className={`m-ant-btn`}
></Button>
}

View File

@ -1,3 +0,0 @@
export const role = {
ID: ['1', '3']
}

View File

@ -1,21 +1,14 @@
import styles from '@/page/Home/Index/index.module.scss'
import { useEffect, useState, useRef } from "react";
import Operation from '@/components/Operation';
import { Button, Input, Modal, Pagination, Empty, message, Popover, Popconfirm, DatePicker } from "antd";
import { GetRoom, PostRoom, GetCheckoutRoomNum, GetRoomRtcToken, DeleteRoom, GetRecord } from '@/api/Home/Index';
import { Button, Input, Modal, Pagination, Empty, message } from "antd";
import { GetRoom, PostRomm, GetCheckoutRoomNum, GetRoomRtcToken } from '@/api/Home/Index';
import ImageUrl from '@/utils/package/imageUrl'
import { ExclamationCircleFilled, ReloadOutlined } from '@ant-design/icons';
import { ReloadOutlined } from '@ant-design/icons';
import JoinSetting from '@/components/JoinSetting';
import { storage } from '@/utils';
import { PostRefresh } from '@/api/Login';
import { useNavigate } from 'react-router-dom';
import { role } from '@/config/role';
import dayjs from 'dayjs';
import StupWizard from '@/components/StupWizard';
const fs = require('fs').promises;
const { exec } = require('child_process');
const { RangePicker } = DatePicker;
const { confirm } = Modal;
const Index: React.FC = () => {
const navigate = useNavigate();
const [list, setList] = useState({
@ -25,15 +18,12 @@ const Index: React.FC = () => {
pageSize: 12,
})
const [createRoomModal, setCreateRoomModal] = useState(false)
const [timeSelectModal, setTimeSelectModal] = useState(false)
const [createRoomFrom, setCreateRoomFrom] = useState<{ roomName: string, roomNum: string }>({
roomName: "",
roomNum: ""
})
const joinSettingRef = useRef<any>();
const stupWizardRef = useRef<any>();
const [user, setUser] = useState<any>({});
const [currentRoomInfo, setCurrentRoomInfo] = useState<any>({});
const userInfo = JSON.parse(storage.getItem('user') as string)
useEffect(() => {
setUser(userInfo)
@ -80,7 +70,7 @@ const Index: React.FC = () => {
})
}
const getRoomRtcToken = async (roomNum: string, callBack: Function): Promise<void> => {
Promise.all([GetRoomRtcToken(roomNum), GetRoomRtcToken(roomNum + 'a')]).then(res => {
Promise.all([GetRoomRtcToken(roomNum), GetRoomRtcToken(roomNum + '1')]).then(res => {
if (res[0].code === 200 && res[1].code === 200) {
callBack({
token: res[0].data,
@ -93,59 +83,10 @@ const Index: React.FC = () => {
await PostRefresh(user.refresh_token).then(res => {
if (res.code === 200) {
storage.setItem('user', JSON.stringify(res.data))
storage.setItem('userLogin', true)
callBack(res.data)
}
})
}
const changeOpen = (index: number, bool: boolean): void => {
const newList = [...list.data] as any;
newList[index].open = bool
setList({
...list,
data: newList
})
}
const fileUpLoad = async (data: { url: string, content: string, fileName: string }): Promise<void> => {
const setting = await JSON.parse(storage.getItem('setting') as string)
try {
const response = await fetch(data.url);
const arrayBuffer = await response.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
await fs.writeFile(`${setting.shareFilesPath}\\${data.fileName}`, buffer, {});
confirm({
title: '提示',
icon: <ExclamationCircleFilled />,
content: data.content,
centered: true,
okText: '打开文件夹',
cancelText: '关闭',
async onOk() {
await fs.access(setting.shareFilesPath, fs.constants.F_OK);
if (process.platform === 'win32') {
exec(`explorer "${setting.shareFilesPath}"`);
} else if (process.platform === 'darwin') {
exec(`open "${setting.shareFilesPath}"`);
}
},
onCancel() {
}
})
} catch (error: any) {
if (error.code === 'ENOENT') {
message.error({
content: <div> <span style={{ color: '#606fc7', cursor: 'pointer' }} onClick={() => {
stupWizardRef.current.changeModal(4)
}}></span></div>
})
return
} else {
message.error(error)
}
}
}
return (
<>
<div className={styles.index}>
@ -206,52 +147,12 @@ const Index: React.FC = () => {
<img src={ImageUrl.icon10} alt="" />
</div>
<div>
<Popover
content={
<div className='meetingContentFooterPopover'>
<Popconfirm
title="提示"
description={`确定删除该会议吗`}
onConfirm={async () => {
DeleteRoom(item.id).then((res) => {
if (res.code === 200) {
message.success('删除成功')
changeOpen(index, false)
getRoomList()
}
})
}}
onCancel={() => {
changeOpen(index, false)
}}
okText="确定"
cancelText="取消"
>
<div></div>
</Popconfirm>
<div onClick={() => {
changeOpen(index, false)
setTimeSelectModal(true)
}}></div>
<div onClick={() => {
changeOpen(index, false)
}}></div>
</div>
}
title=""
trigger="click"
open={item.open}
onOpenChange={() => {
setCurrentRoomInfo(list.data[index])
changeOpen(index, true)
}}
>
<Button type="primary" danger></Button>
</Popover>
{/* <Button type="primary" danger>设置</Button> */}
<Button type="primary"
iconPosition={'end'}
onClick={async () => {
if (role.ID.includes(userInfo.roleId)) {
if (userInfo.roleId === '1') {
joinSettingRef.current.changeModal(item.roomNum)
} else {
postRefresh(() => {
@ -366,7 +267,7 @@ const Index: React.FC = () => {
if (bool) {
message.error('房间号已存在!')
} else {
PostRoom(createRoomFrom).then(res => {
PostRomm(createRoomFrom).then(res => {
if (res.code === 200) {
message.success('创建成功!')
setCreateRoomModal(false)
@ -379,32 +280,7 @@ const Index: React.FC = () => {
</div>
</div>
</Modal>
<Modal title="选择时间段" destroyOnClose={true} open={timeSelectModal} footer={null} onCancel={() => setTimeSelectModal(false)} centered width={'400px'}>
<div>
<RangePicker
showTime={{ format: 'YYYY-MM-DD HH:mm:ss' }}
format="YYYY-MM-DD HH:mm:ss"
onChange={(_value, dateString) => {
const setting = JSON.parse(storage.getItem('setting') as string)
if (dateString.length === 2) {
GetRecord(dayjs(dateString[0]).unix(), dayjs(dateString[1]).unix(), currentRoomInfo.roomNum).then(res => {
if (res.code === 200) {
const fileName = res.data.split('/').pop().split('?')[0];
fileUpLoad({
url: res.data,
content: `下载参会记录成功!文件已保存至:${setting.shareFilesPath}`,
fileName
})
}
setTimeSelectModal(false)
})
}
}}
/>
</div>
</Modal>
<JoinSetting ref={joinSettingRef} />
<StupWizard ref={stupWizardRef} />
</>
)
}

View File

@ -21,14 +21,11 @@
display: flex;
align-items: center;
>button {
margin-right: 22px;
}
.userBtnsDel {
background-color: #3A1457;
box-shadow: none;
color: white;
margin-left: 22px;
&:hover {
background-color: lighten(#3A1457, 5%) !important;

View File

@ -1,19 +1,13 @@
import styles from '@/page/Home/User/index.module.scss'
import { useEffect, useState, useRef } from "react";
import { useEffect, useState } from "react";
import Operation from '@/components/Operation';
import { Button, Input, Table, Pagination, Modal, message, Select } from "antd";
import { ExclamationCircleFilled, SearchOutlined } from '@ant-design/icons';
import { GetUserList, PostUser, PutUser, DeleteUser, PutUserPwd, GetRoleDpList, PostUserImport } from '@/api/Home/User';
import { SearchOutlined } from '@ant-design/icons';
import { GetUserList, PostUser, PutUser, DeleteUser, PutUserPwd, GetRoleDpList } from '@/api/Home/User';
import * as CryptoJS from 'crypto-js';
import ImageUrl from '@/utils/package/imageUrl';
import { storage } from '@/utils';
import StupWizard from '@/components/StupWizard';
const { Column } = Table
const { confirm } = Modal;
const { exec } = require('child_process');
const fs = require('fs').promises;
const User: React.FC = () => {
const stupWizardRef = useRef<any>();
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
const [isCreateUser, setIsCreateUser] = useState(false);
const [list, setList] = useState({
@ -33,7 +27,6 @@ const User: React.FC = () => {
UserName: ""
})
const [changeUserPawModal, setChangeUserPawModal] = useState(false)
const [changeImportModal, setChangeImportModal] = useState(false)
const [changeUserPawFrom, setChangeUserPawFrom] = useState({
Pwd: "",
newPwd: '',
@ -79,46 +72,6 @@ const User: React.FC = () => {
}
})
}
const fileUpLoad = async (data: { url: string, content: string, fileName: string }): Promise<void> => {
const setting = await JSON.parse(storage.getItem('setting') as string)
try {
const response = await fetch(data.url);
const arrayBuffer = await response.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
await fs.writeFile(`${setting.shareFilesPath}\\${data.fileName}`, buffer, {});
setChangeImportModal(false)
confirm({
title: '提示',
icon: <ExclamationCircleFilled />,
content: data.content,
centered: true,
okText: '打开文件夹',
cancelText: '关闭',
async onOk() {
await fs.access(setting.shareFilesPath, fs.constants.F_OK);
if (process.platform === 'win32') {
exec(`explorer "${setting.shareFilesPath}"`);
} else if (process.platform === 'darwin') {
exec(`open "${setting.shareFilesPath}"`);
}
},
onCancel() {
}
})
} catch (error: any) {
if (error.code === 'ENOENT') {
message.error({
content: <div> <span style={{ color: '#606fc7', cursor: 'pointer' }} onClick={() => {
stupWizardRef.current.changeModal(4)
}}></span></div>
})
return
} else {
message.error(error)
}
}
}
return (
<>
<div className={styles.user}>
@ -147,13 +100,6 @@ const User: React.FC = () => {
className='m-ant-btn'>
</Button>
<Button type="primary"
onClick={() => {
setChangeImportModal(true)
}}
className='m-ant-btn'>
</Button>
<Button type="primary"
icon={<img src={ImageUrl.icon21} alt="" />}
className={styles.userBtnsDel}
@ -275,14 +221,17 @@ const User: React.FC = () => {
<Input
style={{ flexGrow: 1 }}
placeholder="请输入账号"
maxLength={30}
maxLength={11}
showCount={true}
value={addUserFrom.Account}
onChange={(e) => {
const regex = /^[0-9]*$/;
if (regex.test(e.target.value)) {
setAddUserFrom({
...addUserFrom,
Account: e.target.value,
});
}
}}
/>
</div>
@ -451,69 +400,6 @@ const User: React.FC = () => {
</div>
</div>
</Modal>
<Modal title='批量导入用户' open={changeImportModal} onCancel={() => setChangeImportModal(false)} footer={null} centered width={'300px'}>
<div>
<div>
<Button type="primary" className='m-ant-btn' style={{ width: '100%', marginBottom: '10px' }}
onClick={async () => {
const setting = await JSON.parse(storage.getItem('setting') as string)
await fileUpLoad({
url: 'https://wgshare.oss-cn-chengdu.aliyuncs.com/%E7%94%A8%E6%88%B7%E6%89%B9%E9%87%8F%E5%AF%BC%E5%85%A5%E6%A8%A1%E6%9D%BF%E8%A1%A8.xlsx',
content: `下载导入模板成功!文件已保存至:${setting.shareFilesPath}`,
fileName: `用户批量导入模板表_${+new Date()}.xlsx`
})
}}
>
</Button>
</div>
<div>
<Button type="primary" className='m-ant-btn' style={{ width: '100%' }}
onClick={() => {
const file = document.createElement("input") as any;
file.type = "file";
file.accept = ".xls,.xlsx";
file.onchange = async () => {
const setting = await JSON.parse(storage.getItem('setting') as string)
const fileInfo = file.files[0];
const formData = new FormData();
formData.append("file", fileInfo);
await PostUserImport(formData).then(res => {
if (res.code === 200) {
if (res.data.item1) {
if (list.pageIndex === 1) {
getUserList()
} else {
setList({
...list,
pageIndex: 1
})
}
message.success(res.data.item3)
} else {
if (res.data.item2) {
const fileName = res.data.item2.split('/').pop().split('?')[0];
fileUpLoad({
url: res.data.item2,
content: `导入模板失败!失败文件已保存至:${setting.shareFilesPath}`,
fileName
})
}
message.error(res.data.item3)
}
}
})
setChangeImportModal(false)
};
file.click();
}}
>
</Button>
</div>
</div>
</Modal>
<StupWizard ref={stupWizardRef} />
</>
)
}

View File

@ -139,8 +139,8 @@ const Home: React.FC = () => {
title="提示"
description="确认退出吗?"
onConfirm={() => {
navigate('/login')
storage.removeItem('user')
storage.setItem('userLogin', false)
}}
onCancel={() => {

View File

@ -140,13 +140,10 @@ const Login: React.FC = () => {
optionsValue: operation.optionsValue,
}))
storage.setItem('user', JSON.stringify(res.data))
storage.setItem('userLogin', true)
try {
window.electron.getWindowSize().then((res: any) => {
window.electron.setMainWindowSize({
width: Math.ceil(res.width / 1.5),
height: Math.ceil(res.height / 1.3),
})
width: 1200,
height: 800,
})
} catch {

View File

@ -31,32 +31,9 @@
>img {
width: 18px;
}
>label {
height: 18px;
width: 18px;
position: relative;
>img {
width: 100%;
height: 100%;
margin-right: 4px;
}
>div {
position: absolute;
left: 0;
bottom: 0;
height: 0%;
width: 100%;
overflow: hidden;
background-repeat: no-repeat;
background-position: bottom center;
background-size: cover;
}
}
>span {
font-size: 13px;
color: #EEEEEE;
@ -183,7 +160,6 @@
height: 100%;
box-sizing: border-box;
position: relative;
overflow: hidden;
.standardModeIcon {
position: absolute;
@ -233,7 +209,6 @@
}
}
// 自由者模式
.meetingContentBodyLeftFreedomMode {
width: 100%;
@ -307,7 +282,7 @@
position: absolute !important;
bottom: 0;
left: 0;
height: calc(100% - 170px) !important;
height: calc(100% - 160px) !important;
width: 100% !important;
z-index: 2;
}
@ -368,7 +343,6 @@
}
}
.meetingContentSwiperCardFullScreenIcon {
position: absolute;
z-index: 3;
@ -420,14 +394,33 @@
padding: 10px 20px;
white-space: nowrap;
}
.meetingContentSwiperCardBorder {
position: absolute;
height: 3px;
width: calc(100% - 20px);
background-color: #2C2C2C;
overflow: hidden;
left: 10px;
z-index: 1;
bottom: 9px;
>div {
background-color: #5575F2;
position: absolute;
height: 3px;
left: 0;
top: 0;
}
}
}
.meetingContentBodyRight {
width: 300px;
flex-shrink: 0;
height: 100%;
.meetingUserList {
width: 300px;
height: 100%;
padding: 10px 0 20px;
box-sizing: border-box;
@ -542,7 +535,6 @@
}
.meetingUserChat {
width: 300px;
height: 100%;
background-color: #16191E;
display: flex;
@ -661,39 +653,6 @@
border-top: 1px solid #23272E;
}
}
.meetingUserVideoList {
padding: 10px 10px 10px;
box-sizing: border-box;
height: 100%;
background-color: #16191E;
display: flex;
flex-direction: column;
.meetingUserVideoListTitle {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
>span {
color: #EEEEEE;
font-size: 18px;
}
>img {
cursor: pointer;
}
}
.meetingUserVideoListContent {
flex-grow: 1;
height: 0px;
overflow-y: auto;
box-sizing: border-box;
}
}
}
}
@ -725,7 +684,7 @@
}
>img {
height: 50px;
height: 30px;
}
>span {
@ -746,31 +705,6 @@
right: -10px;
}
>label {
height: 50px;
height: 50px;
cursor: pointer;
position: relative;
>img {
width: 100%;
height: 100%;
margin-right: 4px;
}
>div {
position: absolute;
left: 0;
bottom: 0;
height: 0%;
width: 100%;
overflow: hidden;
background-repeat: no-repeat;
background-position: bottom center;
background-size: cover;
}
}
&:hover {
background-color: #161A29;
}

File diff suppressed because it is too large Load Diff

View File

@ -2,24 +2,40 @@
width: 100%;
height: 100%;
background-color: rgb(7, 9, 11);
padding: 20px;
box-sizing: border-box;
display: flex;
flex-direction: column;
.userVideoTitle {
text-align: center;
color: white;
font-size: 20px;
flex-shrink: 0;
position: relative;
>span {
cursor: pointer;
position: absolute;
right: 20px;
top: 50%;
transform: translate(0, -50%);
}
}
.userVideoContent {
flex-grow: 1;
display: flex;
flex-direction: column;
padding: 20px 40px 0;
.userVideoContentHeader {
flex-shrink: 0;
margin-bottom: 10px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px;
box-sizing: border-box;
flex-shrink: 0;
margin-bottom: 10px;
padding: 0 10px;
>div:nth-child(1) {
display: flex;
@ -29,16 +45,12 @@
background-color: #181A1D;
border-radius: 4px;
padding: 4px 10px;
box-sizing: border-box;
margin: 0 10px 0 0;
display: flex;
align-items: center;
>span {
color: #EEEEEE;
font-size: 12px;
white-space: nowrap;
margin-right: 4px;
font-size: 14px;
margin-right: 10px;
}
}
}
@ -49,8 +61,8 @@
>span {
color: #EEEEEE;
font-size: 12px;
margin-right: 4px;
font-size: 14px;
margin-right: 10px;
}
}
}
@ -65,9 +77,9 @@
height: 0;
.userVideoContentListItem {
height: 32%;
width: calc(100% / 4 - 8px);
padding: 4px;
height: 20%;
width: calc(100% / 4 - 20px);
padding: 10px;
.userVideoContentListItemVideo {
background-color: #101317;
@ -75,18 +87,6 @@
box-sizing: border-box;
width: 100%;
height: 100%;
position: relative;
>div:nth-child(1) {
background-color: rgb(253, 194, 41);
color: black;
position: absolute;
left: 4px;
bottom: 4px;
font-size: 12px;
z-index: 1;
padding: 2px 4px;
}
}
}
}

View File

@ -1,13 +1,14 @@
import styles from '@/components/UserVideo/index.module.scss'
import { GetPolling } from '@/api/Meeting';
import styles from '@/page/UserVideo/index.module.scss'
import { storage } from '@/utils';
import { agora } from '@/utils/package/agora';
import { CloseOutlined } from '@ant-design/icons';
import { Button, Empty, Select, message } from 'antd';
import { useEffect, useState } from "react";
import { useLocation } from 'react-router';
import { VideoStreamType } from 'agora-electron-sdk';
const UserVideo: React.FC = () => {
const { state } = useLocation();
let userInfo = JSON.parse(storage.getItem('user') as string)
const [user, setUser] = useState<any>({});
const [from, setFrom] = useState<any>({
cycleIntervalList: [
{ value: 30, label: '30秒' },
@ -17,16 +18,19 @@ const UserVideo: React.FC = () => {
],
cycleIntervalValue: 30,
viewPeople: [
{ value: 6, label: '6人' },
{ value: 4, label: '4人' },
{ value: 8, label: '8人' },
{ value: 12, label: '12人' },
// { value: 20, label: '20人' },
{ value: 16, label: '16人' },
{ value: 20, label: '20人' },
],
viewPeopleValue: 6,
viewPeopleValue: 4,
})
const [timeNumber, setTimeNumber] = useState(30);
const [timeStatus, setTimeStatus] = useState(false);
const [userList, setUserList] = useState([]);
useEffect(() => {
setUser(userInfo)
window.addEventListener('customStorageChange', handleCustomStorageChange);
return () => {
window.removeEventListener('customStorageChange', handleCustomStorageChange);
@ -59,14 +63,12 @@ const UserVideo: React.FC = () => {
}, [from.viewPeopleValue])
useEffect(() => {
userList.forEach(async (item: any) => {
await agora.destroyRendererByConfig(Number('1' + item.screenShareId))
await agora.setupRemoteVideoEx({
uid: Number('1' + item.screenShareId),
view: document.getElementById(`video-${item.screenShareId}`),
channelId: state.channelId + 'a',
})
await agora.setRemoteVideoStreamType(Number('1' + item.screenShareId), VideoStreamType.VideoStreamLow, false)
userList.forEach((item: any) => {
// agora.meetingMonitoringSetupRemoteVideoJoin({
// uid: Number('1' + item.screenShareId),
// view: document.getElementById(`video-${item.uid}`),
// channelId: getQueryParameterRegex('channelId'),
// })
})
}, [userList])
// 监听缓存变化
@ -75,22 +77,30 @@ const UserVideo: React.FC = () => {
}
};
// 获取地址栏参数
const getQueryParameterRegex = (name: string): string | null => {
const reg = new RegExp(`[?&]${name}=([^&#]*)`);
const results = window.location.href.match(reg);
return results === null ? null : results[1];
}
// 获取轮训用户
const getPolling = async (): Promise<void> => {
setUserList([])
setFrom((res: any) => {
GetPolling(state.channelId?.split('a')[0] as string, res.viewPeopleValue).then((res: any) => {
GetPolling(getQueryParameterRegex('channelId')?.split('1')[0] as string, from.viewPeopleValue).then((res: any) => {
if (res.code === 200) {
setUserList(res.data)
}
})
return res
})
};
return (
<>
<div className={styles.userVideo}>
<div className={`${styles.userVideoContent}`}>
<div className={styles.userVideoTitle}>
<CloseOutlined className='drag' onClick={() => {
window.electron.closeMonitorWindow()
}} />
</div>
<div className={`${styles.userVideoContent} drag`}>
<div className={styles.userVideoContentHeader}>
<div>
<div>
@ -98,7 +108,6 @@ const UserVideo: React.FC = () => {
<Select
placeholder='请选择循环间隔'
options={from.cycleIntervalList}
size={'small'}
value={from.cycleIntervalValue} onChange={(e) => {
setFrom({ ...from, cycleIntervalValue: e })
setTimeNumber(e)
@ -109,7 +118,6 @@ const UserVideo: React.FC = () => {
<Select
placeholder='请选择查看人数'
options={from.viewPeople}
size={'small'}
value={from.viewPeopleValue} onChange={(e) => {
setFrom({ ...from, viewPeopleValue: e })
}} />
@ -119,7 +127,6 @@ const UserVideo: React.FC = () => {
<span>{timeNumber}</span>
{timeStatus ? <Button
type="primary"
size={'small'}
onClick={() => {
setTimeStatus(!timeStatus)
}}
@ -128,7 +135,6 @@ const UserVideo: React.FC = () => {
<Button
type="primary"
className='m-ant-btn'
size={'small'}
onClick={() => {
setTimeStatus(!timeStatus)
}}
@ -139,8 +145,8 @@ const UserVideo: React.FC = () => {
{
userList.map((item: any, index: number) => {
return <div className={styles.userVideoContentListItem} key={index}>
<div className={styles.userVideoContentListItemVideo} id={`video-${item.screenShareId}`}>
<div>{item.userName}{item.isRoomManager ? '(发言中)' : ''}</div>
<div className={styles.userVideoContentListItemVideo} id={`video-${item.uid}`}>
</div>
</div>
})

4
src/render.d.ts vendored
View File

@ -1,7 +1,6 @@
// electron-env.d.ts
export interface IElectronAPI {
setMainWindowSize: (config: any) => void;
getWindowSize: () => any;
setViewStatus: (status: 'quit' | 'maximize' | 'minimize' | 'unmaximize' | 'hide') => void;
getIsMaximized: () => Promise<boolean>;
setWriteText: (text: string) => void;
@ -16,6 +15,9 @@ export interface IElectronAPI {
downFile: (callBack: Function) => void;
quitAndInstall: (callBack: Function) => void;
getVersion: () => Promise<string>;
oepnWindow: (data: any) => any;
closeMonitorWindow: () => void
}
declare global {

View File

@ -5,16 +5,12 @@ import {
VideoViewSetupMode,
ScreenCaptureSourceType,
RenderModeType,
ChannelProfileType,
AudioAinsMode,
SimulcastStreamMode,
VideoStreamType
ChannelProfileType
} from "agora-electron-sdk";
import { GetRoomRtcToken, GetAgoraConf } from "@/api/Home/Index";
import { GetRoomRtcToken } from "@/api/Home/Index";
import { storage } from '@/utils';
import { role } from "@/config/role";
const option: any = {
appId: '',
appId: 'dcfc466a6ecb4a1f972630065dfb1e75',
token: '',
tokenA: '',
channelId: '',
@ -25,14 +21,11 @@ let rtcEngine: any = '';
export const agora = {
// 初始化
init: async (bool: boolean = false) => {
const { data } = await GetAgoraConf();
if (data) {
rtcEngine = createAgoraRtcEngine();
await rtcEngine.initialize({
appId: data,
appId: option.appId,
});
await agora.setDeviceManager(bool)
}
},
// 获取rtcEngine
getRtcEngine: () => {
@ -99,7 +92,6 @@ export const agora = {
if (setting.playBackVolume) agora.setPlaybackDeviceVolume(setting.playBackVolume) // 设置播放设备音量
if (setting.ecordingDeviceId) agora.setRecordingDevice(setting.ecordingDeviceId) // 设置音频采集设备
if (setting.ecordingVolume) agora.setRecordingDeviceVolume(setting.ecordingVolume) // 设置音频设备音量
if (setting.isAINoiseReduction) agora.setAINSMode(setting.isAINoiseReduction, setting.aINoiseReduction) // 设置ai降噪
}
}, 1000);
},
@ -171,20 +163,6 @@ export const agora = {
);
}
},
setupRemoteVideoEx: async (item: any) => {
if (item.view?.childNodes.length === 1) {
await rtcEngine.setupRemoteVideoEx(
{
renderMode: agora.getRrenderMode(item.uid),
sourceType: VideoSourceType.VideoSourceRemote,
uid: item.uid,
view: item.view,
setupMode: VideoViewSetupMode.VideoViewSetupAdd,
},
{ channelId: item.channelId },
);
}
},
// 退出
setupRemoteVideo: async (item: any) => {
await rtcEngine.setupRemoteVideo(
@ -215,17 +193,6 @@ export const agora = {
joinChannel: async () => {
await rtcEngine.enableAudioVolumeIndication(100, 1, true)
await rtcEngine.joinChannel(option.token, option.channelId, option.uid);
await rtcEngine.setDualStreamModeEx(
SimulcastStreamMode.EnableSimulcastStream,
{
dimensions: {
width: 320,
height: 180
},
framerate: 5,
},
{ channelId: option.channelId, localUid: Number(option.uid) }
);
},
// 更新频道配置
updateChannelMediaOptions: async (bool: boolean) => {
@ -238,14 +205,6 @@ export const agora = {
publishScreenTrack: false,//设置是否发布屏幕采集的视频
})
},
// 设置接收大小流
setRemoteVideoStreamType: async (uid: number, type: VideoStreamType, bool: boolean) => {
await rtcEngine.setRemoteVideoStreamTypeEx(
Number(uid),
type,
bool ? { channelId: option.channelId, localUid: Number(option.uid) } : { channelId: option.channelId + 'a', localUid: Number('1' + option.screenShareId) }
)
},
// 共享屏幕单独用户
joinChannelEx: async (uid: any) => {
await agora.leaveChannelEx(uid)
@ -263,53 +222,26 @@ export const agora = {
);
},
// 所有用户加入的第二个房间
allJoinChannelEx: async (bool: boolean = false) => {
const user = await JSON.parse(storage.getItem('user') as string)
await agora.startCameraCapture(true)
allJoinChannelEx: async () => {
await rtcEngine.joinChannelEx(
option.tokenA,
{ channelId: option.channelId + 'a', localUid: Number('1' + option.screenShareId) },
{
clientRoleType: bool ? ClientRoleType.ClientRoleAudience : ClientRoleType.ClientRoleBroadcaster, //用户角色 ClientRoleBroadcaster 主播 ClientRoleAudience 观众
{ channelId: option.channelId + '1', localUid: Number('1' + option.screenShareId) },
{}
);
await agora.updateChannelMediaOptionsEx(false)
},
updateChannelMediaOptionsEx: async (bool: boolean) => {
rtcEngine.updateChannelMediaOptionsEx({
clientRoleType: bool ? ClientRoleType.ClientRoleBroadcaster : ClientRoleType.ClientRoleAudience, //用户角色 ClientRoleBroadcaster 主播 ClientRoleAudience 观众
autoSubscribeAudio: false,//设置是否自动订阅所有音频流
autoSubscribeVideo: role.ID.includes(user.roleId) ? true : false,//设置是否自动订阅所有视频流
publishMicrophoneTrack: false,//设置是否发布麦克风采集到的音频
autoSubscribeVideo: true,//设置是否自动订阅所有视频流
publishMicrophoneTrack: true,//设置是否发布麦克风采集到的音频
publishCameraTrack: true,//设置是否发布摄像头采集的视频
publishScreenTrack: false,//设置是否发布屏幕采集的视频
}
);
await rtcEngine.setDualStreamModeEx(
SimulcastStreamMode.EnableSimulcastStream,
{
dimensions: {
width: 320,
height: 180
},
framerate: 5,
},
{ channelId: option.channelId + 'a', localUid: Number('1' + option.screenShareId) }
);
},
// 退出第二个房间
allLeaveChannelEx: async () => {
await agora.stopCameraCapture();
await rtcEngine.leaveChannelEx({ channelId: option.channelId + 'a', localUid: Number('1' + option.screenShareId) })
},
// 停止/恢复接收指定的视频流。
muteRemoteVideoStreamEx: async (uid: number, mute: boolean) => {
await rtcEngine.muteRemoteVideoStreamEx(uid, mute, { channelId: option.channelId, localUid: Number(option.uid) })
},
// 取消或恢复订阅指定远端用户的音频流
muteRemoteVideoStream: async (uid: number, mute: boolean) => {
rtcEngine.muteRemoteVideoStream(uid, mute)
},
// 销毁视频渲染dom
destroyRendererByConfig: async (uid: number) => {
await rtcEngine.destroyRendererByConfig(VideoSourceType.VideoSourceRemote, option.channelId + 'a', uid);
},
// ai降噪
setAINSMode: async (enabled: boolean, mode: AudioAinsMode) => {
rtcEngine.setAINSMode(enabled, mode)
}, {
channelId: option.channelId + '1',
localUid: Number('1' + option.screenShareId)
})
},
// 离开共享屏幕频道
leaveChannelEx: async (uid: any) => {
@ -321,27 +253,19 @@ export const agora = {
rtcEngine.enableLoopbackRecording(false)
},
// 取消或恢复发布本地音频流
muteLocalAudioStream: async (data: any, mute: any) => {
// await rtcEngine.muteLocalAudioStream(mute)
await rtcEngine.updateChannelMediaOptions({
clientRoleType: data ? ClientRoleType.ClientRoleBroadcaster : ClientRoleType.ClientRoleAudience, //用户角色 ClientRoleBroadcaster 主播 ClientRoleAudience 观众
autoSubscribeAudio: true,//设置是否自动订阅所有音频流
autoSubscribeVideo: true,//设置是否自动订阅所有视频流
publishMicrophoneTrack: mute,//设置是否发布麦克风采集到的音频
publishCameraTrack: true,//设置是否发布摄像头采集的视频
publishScreenTrack: false,//设置是否发布屏幕采集的视频
})
muteLocalAudioStream: async (mute: any) => {
await rtcEngine.muteLocalAudioStream(mute)
},
// 取消或恢复发布本地视频流
muteLocalVideoStream: async (mute: any) => {
await rtcEngine.muteLocalVideoStream(mute)
},
// 摄像头采集
startCameraCapture: async (bool: boolean = false) => {
startCameraCapture: async () => {
await rtcEngine.startCameraCapture(VideoSourceType.VideoSourceCamera, {
format: {
width: bool ? 160 : 1280,
height: bool ? 160 : 720,
width: 640,
height: 360,
fps: 15,
}
})
@ -358,10 +282,13 @@ export const agora = {
option.uid = Number(data.uid);
option.screenShareId = data.screenShareId;
await agora.joinChannel()
if (data.tokenA) {
await agora.allJoinChannelEx()
}
},
// 桌面捕获音频和视频的媒体源的信息
getDesktopCapturerVideo: async (thumbSize: any, iconSize: any, includeScreen: boolean) => {
return await rtcEngine.getScreenCaptureSources(thumbSize, iconSize, includeScreen).filter((item: any) => item.type === 1)
getDesktopCapturerVideo: async () => {
return rtcEngine.getScreenCaptureSources({ width: 300, height: 300 }, { width: 300, height: 300 }, true);
},
// 共享屏幕采集
setDesktopCapturerVideo: async (targetSource: any, isComputerAudio: boolean, isFluencyPriority: boolean) => {

View File

@ -70,7 +70,6 @@ import icon47 from '@/assets/icon47.png'
import icon47Active from '@/assets/icon47-active.png'
import icon48 from '@/assets/icon48.png'
import icon48Select from '@/assets/icon48-select.png'
import icon49 from '@/assets/icon49.png'
export default {
loading,
icon,
@ -143,6 +142,5 @@ export default {
icon47,
icon47Active,
icon48,
icon48Select,
icon49,
icon48Select
}

View File

@ -160,27 +160,6 @@ export const onSignalr = (callBack: Function) => {
watchUids
})
});
// 设备列表
connection.on("DriverList", (callerUid: string) => {
callBack({
key: 'DriverList',
callerUid
})
});
// 设置设备
connection.on("SaveDriver", (driver: string) => {
callBack({
key: 'SaveDriver',
driver
})
});
// 显示设备列表
connection.on("ShowDriverList", (driversJsonString: string) => {
callBack({
key: 'ShowDriverList',
driversJsonString
})
});
}
}
export const offSignalr = () => {
@ -199,9 +178,6 @@ export const offSignalr = () => {
connection.off('ManagerRefresh');
connection.off('ApplyToSpeak');
connection.off('Watch');
connection.off('DriverList');
connection.off('SetDriver');
connection.off('ShowDriverList');
}
}
export const onInvoke = async (str: string, data: any) => {
@ -213,18 +189,6 @@ export const onInvoke = async (str: string, data: any) => {
// 4:屏幕共享
await connection.invoke(str, data.roomNum, data.type)
break;
case 'getDrivers':
// 获取某个人的设备列表
await connection.invoke(str, data.uid)
break;
case 'sendDrivers':
// 发送设备列表给某个人
await connection.invoke(str, data.uid, data.driversJsonString)
break;
case 'setDrivers':
// 设置某个人的设备列表
await connection.invoke(str, data.uid, data.driversJsonString)
break;
}
}
export const onStop = async () => {

View File

@ -1,5 +1,5 @@
class LocalStorage {
private constructor() { }
private constructor() {}
private static instance: LocalStorage | null = null

View File

@ -60,7 +60,7 @@ class Request {
message.error(resData.message)
}
}
if (resData.code === 1403) {
if (resData.code === 1403 || resData.code === 1000) {
toLogin()
}
return resData
@ -113,7 +113,7 @@ class Request {
}
function toLogin() {
storage.removeItem('user')
storage.setItem('userLogin', false)
location.href = location.origin + '/#/login'
}
function updatePostRefresh() {
let user = JSON.parse(storage.getItem('user') as string);
@ -121,7 +121,6 @@ function updatePostRefresh() {
PostRefresh(user.refresh_token).then((res) => {
if (res.code == 200) {
storage.setItem('user', JSON.stringify(res.data))
storage.setItem('userLogin', true)
} else {
toLogin()
}

View File

@ -343,16 +343,11 @@ $pagination-hover-background-color: #5575F2;
}
.ant-radio-checked .ant-radio-inner {
background-color: $btn-background-color;
border-color: $btn-background-color;
background-color: $btn-background-color;
}
}
:where(.css-dev-only-do-not-override-98ntnt).ant-radio-wrapper .ant-radio-disabled .ant-radio-inner {
background-color: #28282C;
border-color: #28282C;
}
// ant-notification
.ant-notification {
.ant-notification-notice-wrapper {
@ -376,8 +371,3 @@ $pagination-hover-background-color: #5575F2;
}
}
}
// ant-message
.ant-message {
-webkit-app-region: no-drag;
}

View File

@ -60,10 +60,7 @@ export default defineConfig({
RenderModeType,
ScreenCaptureSourceType,
VideoSourceType,
VideoViewSetupMode,
AudioAinsMode,
SimulcastStreamMode,
VideoStreamType
VideoViewSetupMode
} = require("agora-electron-sdk")
export {
createAgoraRtcEngine,
@ -72,10 +69,7 @@ export default defineConfig({
RenderModeType,
ScreenCaptureSourceType,
VideoSourceType,
VideoViewSetupMode,
AudioAinsMode,
SimulcastStreamMode,
VideoStreamType
VideoViewSetupMode
}
`,
})